Spring学习之——手写Spring源码V2.0(实现IOC、DI、MVC、AOP)
前言
在上一篇《Spring学习之——手写Spring源码(V1.0)》中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也对Spring的各组件有了自己的理解和认识,于是乎,在空闲时间把之前手写Spring的代码重构了一遍,遵循了单一职责的原则,使结构更清晰,并且实现了AOP,这次还是只引用一个servlet包,其他全部手写实现。
全部源码照旧放在文章末尾~
开发工具
环境:jdk8 + IDEA + maven
jar包:javax.servlet-2.5
项目结构
具体实现
配置文件
web.xml 与之前一样 并无改变
application.properties 增加了html页面路径和AOP的相关配置
#扫描路径# scanPackage=com.wqfrw #模板引擎路径# templateRoot=template #切面表达式# pointCut=public .* com.wqfrw.service.impl..*ServiceImpl..*(.*) #切面类# aspectClass=com.wqfrw.aspect.LogAspect #切面前置通知# aspectBefore=before #切面后置通知# aspectAfter=after #切面异常通知# aspectAfterThrowing=afterThrowing #切面异常类型# aspectAfterThrowingName=java.lang.Exception
IOC与DI实现
1.在DispatcherServlet的init方法中初始化ApplicationContent;
2.ApplicationContent是Spring容器的主入口,通过创建BeanDefintionReader对象加载配置文件;
3.在BeanDefintionReader中将扫描到的类解析成BeanDefintion返回;
4.ApplicationContent中通过BeanDefintionMap这个缓存来关联BeanName与BeanDefintion对象之间的关系;
5.通过getBean方法,进行Bean的创建并封装为BeanWrapper对象,进行依赖注入,缓存到IoC容器中
/** * 功能描述: 初始化MyApplicationContext * * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月03日 18:54:01 * @param configLocations * @return: **/ public MyApplicationContext(String... configLocations) { this.configLocations = configLocations; try { //1.读取配置文件并解析BeanDefinition对象 beanDefinitionReader = new MyBeanDefinitionReader(configLocations); List<MyBeanDefinition> beanDefinitionList = beanDefinitionReader.loadBeanDefinitions(); //2.将解析后的BeanDefinition对象注册到beanDefinitionMap中 doRegisterBeanDefinition(beanDefinitionList); //3.触发创建对象的动作,调用getBean()方法(Spring默认是延时加载) doCreateBean(); } catch (Exception e) { e.printStackTrace(); } }
/** * 功能描述: 真正触发IoC和DI的动作 1.创建Bean 2.依赖注入 * * @param beanName * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月03日 19:48:58 * @return: java.lang.Object **/ public Object getBean(String beanName) { //============ 创建实例 ============ //1.获取配置信息,只要拿到beanDefinition对象即可 MyBeanDefinition beanDefinition = beanDefinitionMap.get(beanName); //用反射创建实例 这个实例有可能是代理对象 也有可能是原生对象 封装成BeanWrapper统一处理 Object instance = instantiateBean(beanName, beanDefinition); MyBeanWrapper beanWrapper = new MyBeanWrapper(instance); factoryBeanInstanceCache.put(beanName, beanWrapper); //============ 依赖注入 ============ populateBean(beanName, beanDefinition, beanWrapper); return beanWrapper.getWrapperInstance(); }
/** * 功能描述: 依赖注入 * * @param beanName * @param beanDefinition * @param beanWrapper * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月03日 20:09:01 * @return: void **/ private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) { Object instance = beanWrapper.getWrapperInstance(); Class<?> clazz = beanWrapper.getWrapperClass(); //只有加了注解的类才需要依赖注入 if (!(clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyService.class))) { return; } //拿到bean所有的字段 包括private、public、protected、default for (Field field : clazz.getDeclaredFields()) { //如果没加MyAutowired注解的属性则直接跳过 if (!field.isAnnotationPresent(MyAutowired.class)) { continue; } MyAutowired annotation = field.getAnnotation(MyAutowired.class); String autowiredBeanName = annotation.value().trim(); if ("".equals(autowiredBeanName)) { autowiredBeanName = field.getType().getName(); } //强制访问 field.setAccessible(true); try { if (factoryBeanInstanceCache.get(autowiredBeanName) == null) { continue; } //赋值 field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance()); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
MVC实现
1.在DispatcherServlet的init方法中调用initStrategies方法初始化九大核心组件;
2.通过循环BeanDefintionMap拿到每个接口的url、实例对象、对应方法封装成一个HandlerMapping对象的集合,并建立HandlerMapping与HandlerAdapter(参数适配器)的关联;
3.初始化ViewResolver(视图解析器),解析配置文件中模板文件路径(即html文件的路径,其作用类似于BeanDefintionReader);
4.在运行阶段,调用doDispatch方法,根据请求的url找到对应的HandlerMapping;
5.在HandlerMapping对应的HandlerAdapter中,调用handle方法,进行参数动态赋值,反射调用接口方法,拿到返回值与返回页面封装成一个MyModelAndView对象返回;
6.通过ViewResolver拿到View(模板页面文件),在View中通过render方法,通过正则将返回值与页面取值符号进行适配替换,渲染成html页面返回
/** * 功能描述: 初始化核心组件 在Spring中有九大核心组件,这里只实现三种 * * @param context * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月04日 11:51:55 * @return: void **/ protected void initStrategies(MyApplicationContext context) { //多文件上传组件 //initMultipartResolver(context); //初始化本地语言环境 //initLocaleResolver(context); //初始化模板处理器 //initThemeResolver(context); //初始化请求分发处理器 initHandlerMappings(context); //初始化参数适配器 initHandlerAdapters(context); //初始化异常拦截器 //initHandlerExceptionResolvers(context); //初始化视图预处理器 //initRequestToViewNameTranslator(context); //初始化视图转换器 initViewResolvers(context); //缓存管理器(值栈) //initFlashMapManager(context); }
/** * 功能描述: 进行参数适配 * * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月05日 19:41:38 * @param req * @param resp * @param mappedHandler * @return: com.framework.webmvc.servlet.MyModelAndView **/ public MyModelAndView handle(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping mappedHandler) throws Exception { //保存参数的名称和位置 Map<String, Integer> paramIndexMapping = new HashMap<>(); //获取这个方法所有形参的注解 因一个参数可以添加多个注解 所以是一个二维数组 Annotation[][] pa = mappedHandler.getMethod().getParameterAnnotations(); /** * 获取加了MyRequestParam注解的参数名和位置 放入到paramIndexMapping中 */ for (int i = 0; i < pa.length; i++) { for (Annotation annotation : pa[i]) { if (!(annotation instanceof MyRequestParam)) { continue; } String paramName = ((MyRequestParam) annotation).value(); if (!"".equals(paramName.trim())) { paramIndexMapping.put(paramName, i); } } } //方法的形参列表 Class<?>[] parameterTypes = mappedHandler.getMethod().getParameterTypes(); /** * 获取request和response的位置(如果有的话) 放入到paramIndexMapping中 */ for (int i = 0; i < parameterTypes.length; i++) { Class<?> parameterType = parameterTypes[i]; if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class) { paramIndexMapping.put(parameterType.getName(), i); } } //拿到一个请求所有传入的实际实参 因为一个url上可以多个相同的name,所以此Map的结构为一个name对应一个value[] //例如:request中的参数t1=1&t1=2&t2=3形成的map结构: //key=t1;value[0]=1,value[1]=2 //key=t2;value[0]=3 Map<String, String[]> paramsMap = req.getParameterMap(); //自定义初始实参列表(反射调用Controller方法时使用) Object[] paramValues = new Object[parameterTypes.length]; /** * 从paramIndexMapping中取出参数名与位置 动态赋值 */ for (Map.Entry<String, String[]> entry : paramsMap.entrySet()) { //拿到请求传入的实参 String value = entry.getValue()[0]; //如果包含url参数上的key 则动态转型赋值 if (paramIndexMapping.containsKey(entry.getKey())) { //获取这个实参的位置 int index = paramIndexMapping.get(entry.getKey()); //动态转型并赋值 paramValues[index] = caseStringValue(value, parameterTypes[index]); } } /** * request和response单独赋值 */ if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) { int index = paramIndexMapping.get(HttpServletRequest.class.getName()); paramValues[index] = req; } if (paramIndexMapping.containsKey(HttpServletResponse.class.getName())) { int index = paramIndexMapping.get(HttpServletResponse.class.getName()); paramValues[index] = resp; } //方法调用 拿到返回结果 Object result = mappedHandler.getMethod().invoke(mappedHandler.getController(), paramValues); if (result == null || result instanceof Void) { return null; } else if (mappedHandler.getMethod().getReturnType() == MyModelAndView.class) { return (MyModelAndView) result; } return null; } /** * 功能描述: 动态转型 * * @param value String类型的value * @param clazz 实际对象的class * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月04日 16:34:40 * @return: java.lang.Object 实际对象的实例 **/ private Object caseStringValue(String value, Class<?> clazz) throws Exception { //通过class对象获取一个入参为String的构造方法 没有此方法则抛出异常 Constructor constructor = clazz.getConstructor(new Class[]{String.class}); //通过构造方法new一个实例返回 return constructor.newInstance(value); }
/** * 功能描述: 对页面内容进行渲染 * * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月04日 17:54:40 * @param model * @param req * @param resp * @return: void **/ public void render(Map<String, ?> model, HttpServletRequest req, HttpServletResponse resp) throws Exception { StringBuilder sb = new StringBuilder(); //只读模式 读取文件 RandomAccessFile ra = new RandomAccessFile(this.viewFile, "r"); String line = null; while ((line = ra.readLine()) != null) { line = new String(line.getBytes("ISO-8859-1"), "utf-8"); //%{name} Pattern pattern = Pattern.compile("%\\{[^\\}]+\\}", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(line); while (matcher.find()) { String paramName = matcher.group(); paramName = paramName.replaceAll("%\\{|\\}", ""); Object paramValue = model.get(paramName); line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString())); matcher = pattern.matcher(line); } sb.append(line); } resp.setCharacterEncoding("utf-8"); resp.getWriter().write(sb.toString()); }
html页面
404.html
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>页面没有找到</title> </head> <body> <font size="25" color="red">Exception Code : 404 Not Found</font> <br><br><br> @我恰芙蓉王 </body> </html>
500.html
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>服务器崩溃</title> </head> <body> <font size="25" color="red">Exception Code : 500 <br/> 服务器崩溃了~</font> <br/> <br/> <b>Message:%{message}</b> <br/> <b>StackTrace:%{stackTrace}</b> <br/> <br><br><br> @我恰芙蓉王 </body> </html>
index.html
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>自定义SpringMVC模板引擎Demo</title> </head> <center> <h1>大家好,我是%{name}</h1> <h2>我爱%{food}</h2> <font color="red"> <h2>时间:%{date}</h2> </font> <br><br><br> @我恰芙蓉王 </center> </html>
测试接口调用返回页面
404.html 接口未找到
500.html 服务器错误
index.html 正常返回页面
AOP实现
1.参照IOC与DI实现第五点,在对象实例化之后,依赖注入之前,将配置文件中AOP的配置解析至AopConfig中;
2.通过配置的pointCut参数,正则匹配此实例对象的类名与方法名,如果匹配上,将配置的三个通知方法(Advice)与此方法建立联系,生成一个 Map<Method, Map<String, MyAdvice>> methodCache 的缓存;
3.将原生对象、原生对象class、原生对象方法与通知方法的映射关系封装成AdviceSupport对象;
4.如果需要代理,则使用JdkDynamicAopProxy中getProxy方法,获得一个此原生对象的代理对象,并将原生对象覆盖;
5.JdkDynamicAopProxy实现了InvocationHandler接口(使用JDK的动态代理),重写invoke方法,在此方法中执行切面方法与原生对象方法。
/** * 功能描述: 反射实例化对象 * * @param beanName * @param beanDefinition * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月03日 20:08:50 * @return: java.lang.Object **/ private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) { String className = beanDefinition.getBeanClassName(); Object instance = null; try { Class<?> clazz = Class.forName(className); instance = clazz.newInstance(); /** * ===========接入AOP begin=========== */ MyAdviceSupport support = instantiateAopConfig(beanDefinition); support.setTargetClass(clazz); support.setTarget(instance); //如果需要代理 则用代理对象覆盖目标对象 if (support.pointCutMatch()) { instance = new MyJdkDynamicAopProxy(support).getProxy(); } /** * ===========接入AOP end=========== */ factoryBeanObjectCache.put(beanName, instance); } catch (Exception e) { e.printStackTrace(); } return instance; }
/** * 功能描述: 解析配置 pointCut * * @param * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月05日 11:20:21 * @return: void **/ private void parse() { String pointCut = aopConfig.getPointCut() .replaceAll("\\.", "\\\\.") .replaceAll("\\\\.\\*", ".*") .replaceAll("\\(", "\\\\(") .replaceAll("\\)", "\\\\)"); //public .*.com.wqfrw.service..*impl..*(.*) String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4); this.pointCutClassPattern = Pattern.compile(pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1)); methodCache = new HashMap<>(); //匹配方法的正则 Pattern pointCutPattern = Pattern.compile(pointCut); //1.对回调通知进行缓存 Map<String, Method> aspectMethods = new HashMap<>(); try { //拿到切面类的class com.wqfrw.aspect.LogAspect Class<?> aspectClass = Class.forName(this.aopConfig.getAspectClass()); //将切面类的通知方法缓存到aspectMethods Stream.of(aspectClass.getMethods()).forEach(v -> aspectMethods.put(v.getName(), v)); //2.扫描目标类的方法,去循环匹配 for (Method method : targetClass.getMethods()) { String methodString = method.toString(); //如果目标方法有抛出异常 则截取 if (methodString.contains("throws")) { methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim(); } /** * 匹配目标类方法 如果匹配上,就将缓存好的通知与它建立联系 如果没匹配上,则忽略 */ Matcher matcher = pointCutPattern.matcher(methodString); if (matcher.matches()) { Map<String, MyAdvice> adviceMap = new HashMap<>(); //前置通知 if (!(null == aopConfig.getAspectBefore() || "".equals(aopConfig.getAspectBefore()))) { adviceMap.put("before", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectBefore()))); } //后置通知 if (!(null == aopConfig.getAspectAfter() || "".equals(aopConfig.getAspectAfter()))) { adviceMap.put("after", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfter()))); } //异常通知 if (!(null == aopConfig.getAspectAfterThrowing() || "".equals(aopConfig.getAspectAfterThrowing()))) { MyAdvice advice = new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfterThrowing())); advice.setThrowingName(aopConfig.getAspectAfterThrowingName()); adviceMap.put("afterThrowing", advice); } //建立关联 methodCache.put(method, adviceMap); } } } catch (Exception e) { e.printStackTrace(); } }
/** * 功能描述: 返回一个代理对象 * * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月05日 14:17:22 * @param * @return: java.lang.Object **/ public Object getProxy() { return Proxy.newProxyInstance(this.getClass().getClassLoader(), this.support.getTargetClass().getInterfaces(), this); } /** * 功能描述: 重写invoke * * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月05日 20:29:19 * @param proxy * @param method * @param args * @return: java.lang.Object **/ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Map<String, MyAdvice> advices = support.getAdvices(method, support.getTargetClass()); Object result = null; try { //调用前置通知 invokeAdvice(advices.get("before")); //执行原生目标方法 result = method.invoke(support.getTarget(), args); //调用后置通知 invokeAdvice(advices.get("after")); } catch (Exception e) { //调用异常通知 invokeAdvice(advices.get("afterThrowing")); throw e; } return result; } /** * 功能描述: 执行切面方法 * * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月05日 11:09:32 * @param advice * @return: void **/ private void invokeAdvice(MyAdvice advice) { try { advice.getAdviceMethod().invoke(advice.getAspect()); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
/** * @ClassName LogAspect * @Description TODO(切面类) * @Author 我恰芙蓉王 * @Date 2020年08月05日 10:03 * @Version 2.0.0 **/ public class LogAspect { /** * 功能描述: 前置通知 * * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月05日 17:24:30 * @param * @return: void **/ public void before(){ System.err.println("=======前置通知======="); } /** * 功能描述: 后置通知 * * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月05日 17:24:40 * @param * @return: void **/ public void after(){ System.err.println("=======后置通知=======\n"); } /** * 功能描述: 异常通知 * * @创建人: 我恰芙蓉王 * @创建时间: 2020年08月05日 17:24:47 * @param * @return: void **/ public void afterThrowing(){ System.err.println("=======出现异常======="); } }
执行结果
总结
以上只贴出了部分核心实现代码,有兴趣的童鞋可以下载源码调试,具体的注释我都在代码中写得很清楚。
代码已经提交至Git : https://github.com/wqfrw/HandWritingSpringV2.0