昨晚听了咕泡学院tom老师手写Spring框架的直播课,今天自己也来完成一下,构建一个简易的Spring框架,以助于更好的理解Spring的 IOC、DI、MVC的设计思想。
一、 构建思路
创建一个maven项目:
二、 配置阶段
- 引入pom依赖
我们只引入servlet-api依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
- 自定义DispatcherServlet类,重写 init()、doPost()、doGet()方法
public class MyDispatcherServlet extends HttpServlet {
public MyDispatcherServlet (){
super();
}
/**
* 初始化:加载配置文件
*/
@Override
public void init() throws ServletException {
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
/**
* 处理业务
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
- 配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>Chen Web Application</display-name>
<servlet>
<servlet-name>Chenmvc</servlet-name>
<servlet-class>com.chen.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Chenmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
- 编写 application.properties
scanPackage=com.chen.demo
- 自定义注解
- Controller
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
- RequstMapping
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value() default "";
}
- Service
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
- Autowired
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
String value()default "";
}
- RequestParam
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value() default "";
}
- 编写controller和service层,并使用我们自定义的注解
@MyController
@MyRequestMapping("/demo")
public class DemoController {
@MyAutowired
private MyDemoService demoService;
@MyRequestMapping("/query")
public void query(HttpServletRequest req, HttpServletResponse resp,
@MyRequestParam("name") String name){
String s = demoService.get(name);
try {
resp.getWriter().write(s);
} catch (IOException e) {
e.printStackTrace();
}
}
}
业务层:
public interface MyDemoService {
String get(String name);
}
@MyService
public class DemoService implements MyDemoService {
public String get(String name) {
return "My name is:" + name;
}
}
三、 初始化阶段
public class MyDispatcherServlet extends HttpServlet {
// 和web.xml中的param-name一致
private static final String LOCATION = "contextConfigLocation";
// 保存所有的配置信息
private Properties properties = new Properties();
// 保存所有被扫描到的相关类名
private List<String> classNames = new ArrayList<String>();
// 初始化所有Bean
private Map<String,Object> ioc = new HashMap<String, Object>();
// 保存所有的url和方法,创建映射关系
private Map<String, Method> handlerMapping = new HashMap<String, Method>();
public MyDispatcherServlet (){ super(); }
/**
* 初始化:加载配置文件
*/
@Override
public void init(ServletConfig config) throws ServletException {
// 1. 加载配置文件
doLoadConfig(config.getInitParameter(LOCATION));
// 2. 扫描所有相关的类
doScanner(properties.getProperty("scanPackage"));
// 3. 初始化所有相关类的实例,保存至ioc容器中
doInstance();
// 4. 依赖注入
doAutowired();
// 5. 构造HandlerMapping
initHandlerMapping();
// 6. 等待请求,匹配url,调用doGet();或者doPost();方法
// 提示信息
System.out.println("Chen mvcframework is init...");
}
/**
* 将RequestMapping中配置的信息和Method进行关联,并保存这些关系。
*/
private void initHandlerMapping() {
if (ioc.isEmpty()) { return; }
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> aClass = entry.getValue().getClass();
if (!aClass.isAnnotationPresent(MyController.class)) {continue;}
String baseUrl = "";
// 获取controller的url配置
if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping requestMapping = aClass.getAnnotation(MyRequestMapping.class);
baseUrl = requestMapping.value();
}
// 获取Method的url配置
Method[] methods = aClass.getMethods();
for (Method method : methods) {
// 如果没有RequestMapping直接跳过
if (!method.isAnnotationPresent(MyRequestMapping.class)) { continue; }
MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
String url = ("/" + baseUrl + "/" + requestMapping.value())
.replaceAll("/+","/");
handlerMapping.put(url,method);
System.out.println("mapped" + url + method);
}
}
}
/**
* 将初始化到ioc容器里的类,需要赋值的字段进行赋值
*/
private void doAutowired() {
if (ioc.isEmpty()) {return;}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
if (! field.isAnnotationPresent(MyAutowired.class)) {continue;}
MyAutowired autowired = field.getAnnotation(MyAutowired.class);
String beanName = autowired.value().trim();
if ("".equals(beanName)) {
beanName = field.getType().getName();
}
// 开启私有属性访问权限
field.setAccessible(true);
try {
field.set(entry.getValue(),ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
continue;
}
}
}
}
/**
* 初始化所有相关的类,并放入到IOC容器之中;
* IOC容器的key默认是类名首字母小写;
* 如果是自己设置类名,则优先使用自定义的。
*/
private void doInstance() {
if (classNames.size() == 0) {return;}
try {
for (String className : classNames) {
Class<?> aClass = Class.forName(className);
if (aClass.isAnnotationPresent(MyController.class)) {
// 默认将首字母小写作为 beanName
String beanName = firstLetterLowercase(aClass.getSimpleName());
ioc.put(beanName,aClass.newInstance());
} else if (aClass.isAnnotationPresent(MyService.class)) {
MyService service = aClass.getAnnotation(MyService.class);
String beanName = service.value();
// 如果用户自己设置的名字,直接用就行
if (!"".equals(beanName.trim())) {
ioc.put(beanName,aClass.newInstance());
continue;
}
Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> i : interfaces) {
ioc.put(i.getName(),aClass.newInstance());
}
} else {
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 处理类名首字母小写的方法
*/
private String firstLetterLowercase(String s){
char[] chars = s.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
private void doScanner(String packageName) {
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.","/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
// 如果是文件夹,则递归获取
if (file.isDirectory()){
doScanner(packageName + "." + file.getName());
} else {
classNames.add(packageName + "." + file.getName().replace(".class","").trim());
}
}
}
private void doLoadConfig(String location) {
InputStream is = null;
// 通过反射获取文件路径,读取配置文件
try {
is = this.getClass().getClassLoader().getResourceAsStream(location);
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null){is.close();}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
/**
* 处理业务
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
// 匹配对应的方法
doDispatch(req,resp);
} catch (Exception e) {
resp.getWriter().write("500 Exception,Details:\r\n" + Arrays.toString(e.getStackTrace())
.replaceAll("\\[|\\]","").replaceAll(",\\s","\r\n"));
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
if (this.handlerMapping.isEmpty()) {return;}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath,"").replaceAll("/+","/");
if (!this.handlerMapping.containsKey(url)) {
resp.getWriter().write("404 Not Found!");
return;
}
Method method = this.handlerMapping.get(url);
// 获取方法参数列表
Class<?>[] parameterTypes = method.getParameterTypes();
// 获取请求参数
Map<String, String[]> parameterMap = req.getParameterMap();
// 保存参数
Object[] paramValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
Class parameterType = parameterTypes[i];
if (parameterType == HttpServletRequest.class) {
paramValues[i] = req;
continue;
} else if (parameterType == HttpServletResponse.class) {
paramValues[i] = resp;
continue;
} else if (parameterType == String.class) {
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
String value = Arrays.toString(entry.getValue())
.replaceAll("\\[|\\]","")
.replaceAll(",\\s",",");
paramValues[i] = value;
}
}
}
try {
String beanName = firstLetterLowercase(method.getDeclaringClass().getSimpleName());
// 反射
method.invoke(this.ioc.get(beanName),paramValues);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
四、 运行阶段
配置自己的服务器(Tomcat),然后运行项目:
运行的输出结果:
访问页面:
修改值: