用300行代码手写提炼Spring的核心原理
本篇内容定位:
- 具有一年以上的SpringMVC使用经验。
- 希望深入了解Spring源码的人群,对Spring有一个整体的宏观感受。
- 全程手写实现SpringMVC的核心功能,帮助大家更深刻地理解设计模式,从最单一的v1版本一步一步优化为v2版本,最后到v3版本。
mini版Spring实现的基本思路:
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <display-name>Web Application</display-name> <!-- mvcframework config start --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>com.zc.mvcframework.v3.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>dispatcher</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <!-- mvcframework config end --> <!-- welcome page --> <welcome-file-list> <welcome-file>/index.html</welcome-file> </welcome-file-list> </web-app>
application.properties
scanPackage=com.zc.demo
自定义的注解
/** * @author 周聪 */ @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyAutowired { String value() default ""; }
/** * @author 周聪 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyController { String value() default ""; }
/** * @author 周聪 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyRequestMapping { String value() default ""; }
/** * @author 周聪 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyRequestParam { String value() default ""; }
/** * @author 周聪 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyService { String value() default ""; }
DemoController
/** * @ClassName DemoAction * @Author 周聪 * @Date 2021/1/29 10:52 * @Version 1.0 * @Description */ @MyController @MyRequestMapping(value = "/demo") public class DemoAction { @MyAutowired private IDemoService demoService; @MyRequestMapping(value = "query") public void query(HttpServletRequest req, HttpServletResponse resp, @MyRequestParam(value = "name") String name) { // String result = demoService.get(name); String result = "My name is " + name; try { resp.getWriter().write(result); } catch (IOException e) { e.printStackTrace(); } } @MyRequestMapping(value = "add") public void add(HttpServletRequest req, HttpServletResponse resp, @MyRequestParam(value = "a") Integer a, @MyRequestParam(value = "b") Integer b) { try { resp.getWriter().write(a + "+" + b + "=" + (a + b)); } catch (IOException e) { e.printStackTrace(); } } @MyRequestMapping("/remove") public String remove(@MyRequestParam("id") Integer id){ return "" + id; } }
DispatcherServlet的v1版本
package com.zc.mvcframework.v2.servlet; import com.zc.mvcframework.annotation.*; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.net.URL; import java.util.*; /** * @ClassName MyDispatcherServlet * @Author 周聪 * @Date 2021/1/29 11:50 * @Version 1.0 * @Description 用到工厂模式、注册式单例模式、委派模式、模板模式、策略模式(HandlerMappings解决了if...else...) */ public class MyDispatcherServlet extends HttpServlet { /** * 保存application。properties配置文件中的内容 */ private Properties contextConfig = new Properties(); /** * 保存扫描的所有的类名 */ private List<String> classNames = new ArrayList<>(); /** * 传说中的IOC容器,我们来揭开它的神秘面纱 * 为了简化程序,暂时不考虑ConcurrentHashMap,主要还是关注设计思想 */ private Map<String, Object> ioc = new HashMap<String, Object>(); /** * 保存url和method对应关系 */ private Map<String, Method> handlerMappings = new HashMap<String, Method>(); @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 { // 6、调用 运行阶段 try { doDispatch(req, resp); } catch (Exception e) { e.printStackTrace(); resp.getWriter().write("500 Exception, Detail : " + Arrays.toString(e.getStackTrace())); } } /** * 初始化阶段 * * @param config * @throws ServletException */ @Override public void init(ServletConfig config) throws ServletException { // 1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); // 2、扫描相关的类 doScanner(contextConfig.getProperty("scanPackage")); // 3、初始化扫描到的类,并且将它们放入到IOC容器之中 doInstance(); // 4、完成依赖注入 doAutowired(); // 5、初始化HandlerMapping initHandlerMapping(); System.out.println("Spring framework is init"); } /** * 执行请求调用方法 * * @param req * @param resp */ private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { /* basePath:http://localhost:8080/test/ getContextPath:/test getServletPath:/test.jsp getRequestURI:/test/test.jsp getRequestURL:http://localhost:8080/test/test.jsp getRealPath:D:\Tomcat 6.0\webapps\test\ getServletContext().getRealPath:D:\Tomcat 6.0\webapps\test\ getQueryString:p=fuck */ // 绝对路径 String requestURI = req.getRequestURI(); // 处理成相对路径 String contextPath = req.getContextPath(); String url = requestURI.replace(contextPath, "").replaceAll("/+", "/"); if (!this.handlerMappings.containsKey(url)) { resp.getWriter().write("404 Not Found !!!"); return; } Method method = this.handlerMappings.get(url); // 投机取巧的方式 // 通过反射拿到method所在class,拿到class之后还会拿到class的名称 // 再调用toLowerFirstCase活动beanName String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName()); // 获取请求参数 Map<String, String[]> params = req.getParameterMap(); // 获取方法的形参列表 Class<?>[] parameterTypes = method.getParameterTypes(); // 获取方法的参数列表 Parameter[] parameters = method.getParameters(); // 保存赋值参数的位置 Object[] paramValues = new Object[parameterTypes.length]; // 根据参数位置动态赋值 for (int i = 0; i < parameterTypes.length; i++) { Class<?> parameterType = parameterTypes[i]; if (parameterType.equals(HttpServletRequest.class)) { paramValues[i] = req; continue; } else if (parameterType.equals(HttpServletResponse.class)) { paramValues[i] = resp; continue; } MyRequestParam annotation = parameters[i].getAnnotation(MyRequestParam.class); if (params.containsKey(annotation.value())) { // 拿到key所对应的值,而拿到的这个值,有一对多的关系,一个key对应一个数组 // http://127.0.0.1:8080/Spring-1.0/demo/query?name=zhangsan&name=lisi&name=wangwu System.out.println(Arrays.toString(params.get(annotation.value()))); // 类型的强制转换 paramValues[i] = convert(parameterType, Arrays.toString(params.get(annotation.value()))); } } // 第一个参数:方法所在的实例,第二个参数:调用时所需要的参数 method.invoke(ioc.get(beanName), paramValues); } /** * url传过来的参数都是String类型的,http是基于字符串协议 * 只需要把String转换成任意类型 * * @param type * @param value * @return */ private Object convert(Class<?> type, String value) { // 去掉数组的中括号 value = value.replaceAll("\\[|\\]", "") .replaceAll("\\s", ""); // 如果是int if (Integer.class == type) { return Integer.valueOf(value); } // 如果还有double或者其他类型,继续加if // 这时候我们应该想到策略模式了 // 这里暂时不实现 return value; } /** * 初始化url和Method绑定关系 */ private void initHandlerMapping() { if (ioc.isEmpty()) { return; } for (Map.Entry<String, Object> entry : ioc.entrySet()) { Class<?> clazz = entry.getValue().getClass(); if (!clazz.isAnnotationPresent(MyController.class)) { continue; } // 保存写在类上面的@MyRequestMapping(name = "/demo") String baseUrl = ""; if (clazz.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class); baseUrl = annotation.value(); } // 默认获取所有的public方法 for (Method method : clazz.getMethods()) { if (!method.isAnnotationPresent(MyRequestMapping.class)) { continue; } MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class); // 优化,多个 / 替换成一个 ,用户不管自己写不写/,我都加一个/ String url = ("/" + baseUrl + "/" + annotation.value()).replaceAll("/+", "/"); handlerMappings.put(url, method); System.out.println("Mapped : " + url + " , " + method); } } } /** * 依赖注入 */ private void doAutowired() { if (ioc.isEmpty()) { return; } for (Map.Entry<String, Object> entry : ioc.entrySet()) { // Declared 获取所有的,特定的字段 包括private/protected/default // 正常来说,普通的OOP变成只能拿到public属性 Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if (!field.isAnnotationPresent(MyAutowired.class)) { continue; } // 自定义beanName赋值 MyAutowired annotation = field.getAnnotation(MyAutowired.class); String beanName = annotation.value().trim(); if ("".equals(beanName)) { // 根据接口类型赋值 beanName = field.getType().getName(); } // 如果是public以外的修饰符,只要加了Autowired注解,都要强制赋值,暴力访问 field.setAccessible(true); try { // 用反射机制,动态给字段赋值 field.set(entry.getValue(), ioc.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } /** * 初始化,为DI做准备 */ private void doInstance() { if (classNames.isEmpty()) { return; } try { for (String className : classNames) { Class<?> clazz = Class.forName(className); // 什么样的类才需要初始化? // 加了注解的类,才初始化,怎么判断? // 为了简化代码逻辑,主要体会设计思想,只举例@Controller和@Service // @Component ....就不一一举例了 if (clazz.isAnnotationPresent(MyController.class)) { Object instance = clazz.newInstance(); // Spring默认类名首字母小写 String beanName = toLowerFirstCase(clazz.getSimpleName()); ioc.put(beanName, instance); } else if (clazz.isAnnotationPresent(MyService.class)) { // 1、自定义的beanName MyService annotation = clazz.getAnnotation(MyService.class); String beanName = annotation.value(); // 2、默认类名首字母小写 if ("".equals(annotation.value().trim())) { beanName = toLowerFirstCase(clazz.getSimpleName()); } Object instance = clazz.newInstance(); ioc.put(beanName, instance); // 3、根据类型自动赋值,投机取巧的方式 for (Class<?> i : clazz.getInterfaces()) { if (ioc.containsKey(i.getName())) { throw new Exception("The “" + i.getName() + "” is exists !"); } ioc.put(i.getName(), instance); } } else { continue; } } } catch (Exception e) { e.printStackTrace(); } } /** * 类名首字母小写 * 如果类名本身是小写字母确实会出现问题 * 但是我要说明的是:这个方法是我自己用,private的 * 传值也是自己传,类也都遵循了驼峰命名法 * 默认传入的值,不存在首字母小写的情况,也不可能出现非首字母的情况 * 为了简化简化程序就不做判断了 * * @param simpleName * @return */ private String toLowerFirstCase(String simpleName) { char[] chars = simpleName.toCharArray(); // 之所以加32,是因为大小写字母的ASCII码相差32 // 大写字母的ASCII码要小于小写字母的ASCII码 // 在Java中,对char做算学运算,实际上就是对ASCII码做算学运算 chars[0] += 32; return String.valueOf(chars); } /** * 扫描出相关的类 * * @param scanPackage */ private void doScanner(String scanPackage) { // scanPackage = com.zc.demo 存储的是包路径 // 转换为文件路径,实际上就是把.替换成/ URL url = this.getClass().getClassLoader().getResource(scanPackage.replaceAll("\\.", "/")); File classPath = new File(url.getFile()); for (File file : classPath.listFiles()) { if (file.isDirectory()) { doScanner(scanPackage + "." + file.getName()); } else { if (!file.getName().endsWith(".class")) { continue; } String className = scanPackage + "." + file.getName().replace(".class", ""); this.classNames.add(className); } } } /** * 加载配置文件 * * @param contextConfigLocation */ private void doLoadConfig(String contextConfigLocation) { // 直接从类路径下找到Spring主配置文件所在的路径 // 并且将其读取进来放到Properties对象中 // 相当于scanPackage=com.zc.demo 从文件中保存到了内存中 InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { contextConfig.load(resourceAsStream); } catch (IOException e) { e.printStackTrace(); } finally { if (resourceAsStream != null) { try { resourceAsStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
DispatcherServlet的v2版本
package com.zc.mvcframework.v3.servlet; import com.zc.mvcframework.annotation.*; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.net.URL; import java.util.*; /** * @ClassName MyDispatcherServlet * @Author 周聪 * @Date 2021/1/29 11:50 * @Version 1.0 * @Description */ public class MyDispatcherServlet extends HttpServlet { /** * 保存application。properties配置文件中的内容 */ private Properties contextConfig = new Properties(); /** * 保存扫描的所有的类名 */ private List<String> classNames = new ArrayList<>(); /** * 传说中的IOC容器,我们来揭开它的神秘面纱 * 为了简化程序,暂时不考虑ConcurrentHashMap,主要还是关注设计思想 */ private Map<String, Object> ioc = new HashMap<String, Object>(); /** * 保存url和method对应关系 */ // private Map<String, Method> handlerMappings = new HashMap<String, Method>(); /** * 思考: 为什么不用Map * 用map的话 key只能是url * HandlerMapping本身的功能就是把url和method对应关系,已经具备了Map的功能 * 根据设计原则:冗余,单一职责,最少知道原则 * 从性能上来说,与其交给Map去遍历不然自己遍历,map内部也是list */ private List<HandlerMapping> handlerMappings = new ArrayList<HandlerMapping>(); @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 { // 6、调用 运行阶段 try { doDispatch(req, resp); } catch (Exception e) { e.printStackTrace(); resp.getWriter().write("500 Exception, Detail : " + Arrays.toString(e.getStackTrace())); } } /** * 初始化阶段 * * @param config * @throws ServletException */ @Override public void init(ServletConfig config) throws ServletException { // 1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); // 2、扫描相关的类 doScanner(contextConfig.getProperty("scanPackage")); // 3、初始化扫描到的类,并且将它们放入到IOC容器之中 doInstance(); // 4、完成依赖注入 doAutowired(); // 5、初始化HandlerMapping initHandlerMapping(); System.out.println("Spring framework is init"); } /** * 执行请求调用方法 * * @param req * @param resp */ private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { /* basePath:http://localhost:8080/test/ getContextPath:/test getServletPath:/test.jsp getRequestURI:/test/test.jsp getRequestURL:http://localhost:8080/test/test.jsp getRealPath:D:\Tomcat 6.0\webapps\test\ getServletContext().getRealPath:D:\Tomcat 6.0\webapps\test\ getQueryString:p=fuck */ HandlerMapping handlerMapping = getHandler(req); if (handlerMapping == null){ resp.getWriter().write("404 Not Found !!!"); return; } // 获得形参列表 Class<?>[] paramTypes = handlerMapping.getParamTypes(); Object[] paramValues = new Object[paramTypes.length]; Map<String, String[]> parameterMap = req.getParameterMap(); for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { String value = Arrays.toString(entry.getValue()); if (!handlerMapping.paramIndexMapping.containsKey(entry.getKey())){ continue; } Integer index = handlerMapping.paramIndexMapping.get(entry.getKey()); // 类型转换 paramValues[index] = convert(paramTypes[index],value); if (handlerMapping.paramIndexMapping.containsKey(HttpServletRequest.class.getName())){ Integer reqIndex = handlerMapping.paramIndexMapping.get(HttpServletRequest.class.getName()); paramValues[reqIndex] = req; } if (handlerMapping.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) { Integer respIndex = handlerMapping.paramIndexMapping.get(HttpServletResponse.class.getName()); paramValues[respIndex] = resp; } } Object returnValue = handlerMapping.method.invoke(handlerMapping.controller, paramValues); if (returnValue instanceof Void || returnValue == null) {return;} resp.getWriter().write(returnValue.toString()); } /** * 根据请求路径获取handlerMapping * @param req * @return */ private HandlerMapping getHandler(HttpServletRequest req) { if (handlerMappings.isEmpty()){ return null; } // 绝对路径 String requestURI = req.getRequestURI(); // 处理成相对路径 String contextPath = req.getContextPath(); String url = requestURI.replace(contextPath, "").replaceAll("/+", "/"); for (HandlerMapping handlerMapping : this.handlerMappings) { if (handlerMapping.getUrl().equals(url)){ return handlerMapping; } } return null; } /** * url传过来的参数都是String类型的,http是基于字符串协议 * 只需要把String转换成任意类型 * * @param type * @param value * @return */ private Object convert(Class<?> type, String value) { // 去掉数组的中括号 value = value.replaceAll("\\[|\\]", "") .replaceAll("\\s", ""); // 如果是int if (Integer.class == type) { return Integer.valueOf(value); } // 如果还有double或者其他类型,继续加if // 这时候我们应该想到策略模式了 // 这里暂时不实现 return value; } /** * 初始化url和Method绑定关系 */ private void initHandlerMapping() { if (ioc.isEmpty()) { return; } for (Map.Entry<String, Object> entry : ioc.entrySet()) { Class<?> clazz = entry.getValue().getClass(); if (!clazz.isAnnotationPresent(MyController.class)) { continue; } // 保存写在类上面的@MyRequestMapping(name = "/demo") String baseUrl = ""; if (clazz.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class); baseUrl = annotation.value(); } // 默认获取所有的public方法 for (Method method : clazz.getMethods()) { if (!method.isAnnotationPresent(MyRequestMapping.class)) { continue; } MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class); // 优化,多个 / 替换成一个 ,用户不管自己写不写/,我都加一个/ String url = ("/" + baseUrl + "/" + annotation.value()).replaceAll("/+", "/"); handlerMappings.add(new HandlerMapping(url,entry.getValue(),method)); System.out.println("Mapped : " + url + " , " + method); } } } /** * 依赖注入 */ private void doAutowired() { if (ioc.isEmpty()) { return; } for (Map.Entry<String, Object> entry : ioc.entrySet()) { // Declared 获取所有的,特定的字段 包括private/protected/default // 正常来说,普通的OOP变成只能拿到public属性 Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if (!field.isAnnotationPresent(MyAutowired.class)) { continue; } // 自定义beanName赋值 MyAutowired annotation = field.getAnnotation(MyAutowired.class); String beanName = annotation.value().trim(); if ("".equals(beanName)) { // 根据接口类型赋值 beanName = field.getType().getName(); } // 如果是public以外的修饰符,只要加了Autowired注解,都要强制赋值,暴力访问 field.setAccessible(true); try { // 用反射机制,动态给字段赋值 field.set(entry.getValue(), ioc.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } /** * 初始化,为DI做准备 */ private void doInstance() { if (classNames.isEmpty()) { return; } try { for (String className : classNames) { Class<?> clazz = Class.forName(className); // 什么样的类才需要初始化? // 加了注解的类,才初始化,怎么判断? // 为了简化代码逻辑,主要体会设计思想,只举例@Controller和@Service // @Component ....就不一一举例了 if (clazz.isAnnotationPresent(MyController.class)) { Object instance = clazz.newInstance(); // Spring默认类名首字母小写 String beanName = toLowerFirstCase(clazz.getSimpleName()); ioc.put(beanName, instance); } else if (clazz.isAnnotationPresent(MyService.class)) { // 1、自定义的beanName MyService annotation = clazz.getAnnotation(MyService.class); String beanName = annotation.value(); // 2、默认类名首字母小写 if ("".equals(annotation.value().trim())) { beanName = toLowerFirstCase(clazz.getSimpleName()); } Object instance = clazz.newInstance(); ioc.put(beanName, instance); // 3、根据类型自动赋值,投机取巧的方式 for (Class<?> i : clazz.getInterfaces()) { if (ioc.containsKey(i.getName())) { throw new Exception("The “" + i.getName() + "” is exists !"); } ioc.put(i.getName(), instance); } } else { continue; } } } catch (Exception e) { e.printStackTrace(); } } /** * 类名首字母小写 * 如果类名本身是小写字母确实会出现问题 * 但是我要说明的是:这个方法是我自己用,private的 * 传值也是自己传,类也都遵循了驼峰命名法 * 默认传入的值,不存在首字母小写的情况,也不可能出现非首字母的情况 * 为了简化简化程序就不做判断了 * * @param simpleName * @return */ private String toLowerFirstCase(String simpleName) { char[] chars = simpleName.toCharArray(); // 之所以加32,是因为大小写字母的ASCII码相差32 // 大写字母的ASCII码要小于小写字母的ASCII码 // 在Java中,对char做算学运算,实际上就是对ASCII码做算学运算 chars[0] += 32; return String.valueOf(chars); } /** * 扫描出相关的类 * * @param scanPackage */ private void doScanner(String scanPackage) { // scanPackage = com.zc.demo 存储的是包路径 // 转换为文件路径,实际上就是把.替换成/ URL url = this.getClass().getClassLoader().getResource(scanPackage.replaceAll("\\.", "/")); File classPath = new File(url.getFile()); for (File file : classPath.listFiles()) { if (file.isDirectory()) { doScanner(scanPackage + "." + file.getName()); } else { if (!file.getName().endsWith(".class")) { continue; } String className = scanPackage + "." + file.getName().replace(".class", ""); this.classNames.add(className); } } } /** * 加载配置文件 * * @param contextConfigLocation */ private void doLoadConfig(String contextConfigLocation) { // 直接从类路径下找到Spring主配置文件所在的路径 // 并且将其读取进来放到Properties对象中 // 相当于scanPackage=com.zc.demo 从文件中保存到了内存中 InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { contextConfig.load(resourceAsStream); } catch (IOException e) { e.printStackTrace(); } finally { if (resourceAsStream != null) { try { resourceAsStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 保存一个url和一个Method的关系 */ public class HandlerMapping{ // url只能放在里面 private String url; private Method method; private Object controller; private Class<?>[] paramTypes; // 形参列表,参数名字作为key,参数的位置顺序作为值 private Map<String, Integer> paramIndexMapping; public HandlerMapping(String url, Object controller, Method method) { this.url = url; this.method = method; this.controller = controller; paramTypes = method.getParameterTypes(); this.paramIndexMapping = new HashMap<String, Integer>(); putParamIndexMapping(method); } private void putParamIndexMapping(Method method) { // 提取方法中加了注解的参数 // 因为一个参数可以有多个注解,而一个方法又有多个参数,所以是一个二维数组 Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for (int i = 0; i < parameterAnnotations.length; i++) { for (Annotation annotation : parameterAnnotations[i]) { if ( annotation instanceof MyRequestParam){ String value = ((MyRequestParam) annotation).value(); if (!"".equals(value.trim())){ paramIndexMapping.put(value,i); } } } } // 提取方法中的request和response参数 Class<?>[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { Class<?> parameterType = parameterTypes[i]; if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class){ paramIndexMapping.put(parameterType.getName(),i); } } } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public Object getController() { return controller; } public void setController(Object controller) { this.controller = controller; } public Class<?>[] getParamTypes() { return paramTypes; } public void setParamTypes(Class<?>[] paramTypes) { this.paramTypes = paramTypes; } public Map<String, Integer> getParamIndexMapping() { return paramIndexMapping; } public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) { this.paramIndexMapping = paramIndexMapping; } } }
总结
面试题1:Spring中的Bean是线程安全的吗?Spring中的Bean是哪里来的?
回答:spring的bean是通过扫描,然后利用反射new出来的,并且缓存在IOC容器中(CrrentHashMap),Spring并没有对你的Bean做任何处理,Bean是不是线程安全取决于Bean本事。
面试题2:Spring中的Bean是如何被回收的?GC回收?不回收?(面试官喜欢埋坑,你是否了解原理)其实就是Spring中的bean的生命周期的问题,不同作用域存活时间不同
回答:Spring中的bean生命周期有:
- 1、singleton:单例,默认作用域。
- 2、prototype:原型,每次创建一个新对象。
- 3、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。
- 4、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。
- 5、global-session:全局会话,所有会话共享一个实例。
GC回收原则:当Bean的引用没有指向任何地方的时候
Singleton :Map<String,Object> ioc 本事就是单例,基于Spring上下文的,Spring上下文不消失,ioc也就不消失,保存的Bean也不消失,所以singleton对象是随着Spring的存亡而存亡
欢迎批评指正。 附:300行代码手写Spring源码地址