title: 暑假最后几天!肝爆(手写)一个Spring之Day5
date: 2022-08-17 23:26:59
categories:
- 手写spring
Day05—AOP代码织入
基础配置
-
因为使用properties代替了xml,所以在application.properties中加入,简化操作
#多切面配置可以在key前面加前缀 #例如 aspect.logAspect. #切面表达式,expression# pointCut=public .* com.czh.spring.demo.service..*ServiceImpl..*(.*) #切面类# aspectClass=com.czh.spring.demo.aspect.LogAspect #切面前置通知# aspectBefore=before #切面后置通知# aspectAfter=after #切面异常通知# aspectAfterThrow=afterThrowing #切面异常类型# aspectAfterThrowingName=java.lang.Exception
完成AOP顶层设计
-
JoinPoint,定义一个切点的抽象,这是AOP的基础组成单元。可以理解为是业务方法的附加信息。
package com.czh.spring.framework.aop.aspect; import java.lang.reflect.Method; /** * 回调连接点,通过它可以获得被代理的业务方法的所有信息 */ public interface MyJoinPoint { Method getMethod();//业务方法本身 Object[] getArguments();//该方法的实参列表 Object getThis();//该方法所属的实例对象 //在JoinPoint中添加自定义属性 void setUserAttribute(String key,Object value); //从已添加的自定义属性中获取一个属性 Object getUserAttribute(String key); }
-
MethodInterceptor,方法拦截器,AOP代码增强的基本组成单元,子类主要有MethodBeforeAdvice,AfterReturningAdvice,AfterThrowingAdvice.
package com.czh.spring.framework.aop.intercept; /** * 方法拦截器顶层接口 */ public interface MyMethodInterceptor { Object invoke(MyMethodInvocation mi)throws Throwable; }
-
AopConfig,定义AOP的配置信息的封装对象,以方便在之后的代码中相互传递。
package com.czh.spring.framework.aop.config; import lombok.Data; /** * AOP配置封装 */ @Data public class MyAopConfig { /*与properties文件中的属性一一对应*/ //切面表达式 private String pointCut; //前置通知方法名 private String aspectBefore; //后置通知方法名 private String aspectAfter; //要植入的切面类 private String aspectClass; //异常通知方法名 private String aspectAfterThrow; //需要通知的异常类型 private String aspectAfterThrowingName; }
-
AdvisedSupport,主要完成对AOP配置的解析。其中PointCutMatch()方法用来判断目标类是否符合切面规则,从而决定是否生成代理类,对目标方法进行增强。getInterceptorAndDynamicInterceptionAdvice()则主要根据AOP配置,将需要回调的方法封装成一个拦截器链并返回提供给外部获取。
package com.czh.spring.framework.aop.support; import com.czh.spring.framework.aop.config.MyAopConfig; import java.lang.reflect.Method; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 主要用来解析和封装AOP配置 */ public class MyAdvisedSupport { private Class targetClass; private Object target; private Pattern pointCutClassPattern; private transient Map<Method, List<Object>> methodCache; private MyAopConfig aopConfig; public MyAdvisedSupport(MyAopConfig aopConfig) { this.aopConfig = aopConfig; } public Class getTargetClass() { return targetClass; } public void setTargetClass(Class targetClass) { this.targetClass = targetClass; parse(); } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } public List<Object> getInterceptorAndDynamicInterceptionAdvice(Method method,Class<?> targetClass)throws Exception{ List<Object> cached = methodCache.get(method); //缓存未命中,则进行下一步处理 if(null == cached){ Method m = targetClass.getMethod(method.getName(), method.getParameterTypes()); cached = methodCache.get(m); //存入缓存 this.methodCache.put(m,cached); } return cached; } public boolean pointCutMatch(){ return pointCutClassPattern.matcher(this.targetClass.toString()).matches(); } private void parse(){ //pointCut表达式 String pointCut = aopConfig.getPointCut() .replaceAll("\\.","\\\\.") .replaceAll("\\\\.\\*",".*") .replaceAll("\\(","\\\\(") .replaceAll("\\)","\\\\)"); String pointCutForClass = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4); pointCutClassPattern = Pattern.compile("class " + pointCutForClass.substring(pointCutForClass.lastIndexOf(" ") + 1)); methodCache = new HashMap<Method,List<Object>>(); Pattern pattern = Pattern.compile(pointCut); try { Class<?> aspectClass = Class.forName(aopConfig.getAspectClass()); HashMap<String, Method> aspectMethods = new HashMap<>(); for (Method m : aspectClass.getMethods()) { aspectMethods.put(m.getName(),m); } //在这里得到的都是原生方法 for (Method m : targetClass.getMethods()) { String methodString = m.toString(); if(methodString.contains("throw")){ methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim(); } Matcher matcher = pattern.matcher(methodString); if(matcher.matches()){ //能够满足切面规则的类,添加到AOP配置中 LinkedList<Object> advices = new LinkedList<>(); //前置通知 if(!(null == aopConfig.getAspectBefore() || "".equals(aopConfig.getAspectBefore().trim()))){ advices.add(new MyMethodBeforeAdvice(aspectMethods.get(aopConfig.getAspectBefore()),aspectClass.newInstance())); } //后置通知 if(!(null == aopConfig.getAspectAfter() || "".equals(aopConfig.getAspectAfter().trim()))){ advices.add(new MyAfterReturningAdvice(aspectMethods.get(aopConfig.getAspectAfter()),aspectClass.newInstance())); } //异常通知 if(!(null == aopConfig.getAspectAfterThrow() || "".equals(aopConfig.getAspectAfterThrow().trim()))){ MyAfterThrowingAdvice afterThrowingAdvice = new MyAfterThrowingAdvice(aspectMethods.get(aopConfig.getAspectAfterThrow()), aspectClass.newInstance()); afterThrowingAdvice.setThrowingName(aopConfig.getAspectAfterThrowingName()); advices.add(afterThrowingAdvice); } methodCache.put(m,advices); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
-
AopProxy,代理工厂的顶层接口,主要是CglibAopProxy和JdkDynamicAopProxy这二个子类分别实现CGlib和JDK Proxy的代理
package com.czh.spring.framework.aop; /** * 代理工厂的顶层接口,提供获取代理对象的顶层入口 * 默认就使用JDK的动态代理 */ public interface MyAopProxy { //获取一个代理对象 Object getProxy(); //通过自定义类加载器获得一个代理对象 Object getProxy(ClassLoader classLoader); }
-
CGlibAopProxy,暂不做实现
package com.czh.spring.framework.aop; import com.czh.spring.framework.aop.support.MyAdvisedSupport; /** * TODO 使用CGlib生成代理类 */ public class MyCglibAopProxy implements MyAopProxy{ private MyAdvisedSupport config; public MyCglibAopProxy(MyAdvisedSupport advisedSupport){ this.config = advisedSupport; } @Override public Object getProxy() { return null; } @Override public Object getProxy(ClassLoader classLoader) { return null; } }
-
JdkDynamicAopProxy,主要功能在invoke()方法中。主要是调用AdvisedSupport的getInterceptorAndDynamicInterceptionAdvice()方法获得拦截器链。在目标类中,每一个被增强的目标方法都对应一个拦截器链。
package com.czh.spring.framework.aop; import com.czh.spring.framework.aop.support.MyAdvisedSupport; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; /** * 使用JDK Proxy API来生成代理类 */ public class MyJdkDynamicAopProxy implements MyAopProxy, InvocationHandler { private MyAdvisedSupport config; public MyJdkDynamicAopProxy(MyAdvisedSupport config) { this.config = config; } //传入原生对象 @Override public Object getProxy() { return getProxy(this.config.getTargetClass().getClassLoader()); } @Override public Object getProxy(ClassLoader classLoader) { return Proxy.newProxyInstance(classLoader,this.config.getTargetClass().getInterfaces(),this); } //invoke()方法是执行代理的关键入口 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //将每一个JoinPoint也就是被代理的业务方法(Method)封装成一个拦截器,组合成一个拦截器链 List<Object> interceptorAndDynamicMethodMatchers = config.getInterceptorAndDynamicInterceptionAdvice(method, this.config.getTargetClass()); //交给拦截器链MethodInvocation的proceed()方法执行 MyMethodInvocation invocation = new MyMethodInvocation(proxy, this.config.getTarget(), method, args, this.config.getTargetClass(), interceptorAndDynamicMethodMatchers); return invocation.proceed(); } }
-
MethodInvocation:关键方法是proceed(),先进行判断,如果拦截器为空,则说明目标方法无需增强,直接调用目标方法并返回。如果拦截器链不为空,则将拦截器链中的方法按顺序执行,直到拦截器链中所有方法全部执行完毕。
package com.czh.spring.framework.aop.intercept; import com.czh.spring.framework.aop.aspect.MyJoinPoint; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 执行拦截器链,相当于Spring中ReflectiveMethodInvocation的功能 */ public class MyMethodInvocation implements MyJoinPoint { private Object proxy;//代理对象 private Method method;//代理的目标方法 private Object target;//代理的目标对象 private Class<?> targetClass;//代理的目标类 private Object[] arguments;//代理的方法的实参列表 private List<Object> interceptorsAndDynamicMethodMatchers;//回调方法链 //保存自定义属性 private Map<String,Object> userAttributes; private int currentInterceptorIndex = -1; public MyMethodInvocation(Object proxy, Object target, Method method, Object[] arguments,Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) { this.proxy = proxy; this.method = method; this.target = target; this.targetClass = targetClass; this.arguments = arguments; this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; } public Object proceed() throws Throwable { //如果Interceptor执行完了,则执行joinPoint if(this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()-1){ return this.method.invoke(this.target,this.arguments); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //如果要动态匹配joinPoint if(interceptorOrInterceptionAdvice instanceof MyMethodInterceptor){ MyMethodInterceptor mi = (MyMethodInterceptor)interceptorOrInterceptionAdvice; return mi.invoke(this); } else { //执行当前Interceptor return proceed(); } } @Override public Method getMethod() { return this.method; } @Override public Object[] getArguments() { return this.arguments; } @Override public Object getThis() { return this.target; } @Override public void setUserAttribute(String key, Object value) { if(null != value){ if(null == this.userAttributes){ this.userAttributes = new HashMap<String,Object>(); } this.userAttributes.put(key, value); }else { if(null != this.userAttributes){ this.userAttributes.remove(key); } } } @Override public Object getUserAttribute(String key) { return (this.userAttributes != null ? this.userAttributes.get(key):null); } }
设计AOP基础实现
-
Advice作为所有回调通知的顶层接口设计,暂时只是作为规范,没实现任何功能。
package com.czh.spring.framework.aop.aspect; /** * 回调通知顶层接口 */ public interface MyAdvice { }
-
AbstractAspectJAdvice:使用模版设计MyAbstractAspectJAdvice类,封装拦截器回调的通用逻辑,主要封装反射动态调用方法,其子类只需要控制调用顺序即可。
package com.czh.spring.framework.aop.aspect; import java.lang.reflect.Method; /** * 封装拦截器回调的通用逻辑,主要封装了反射动态调用方法 */ public class MyAbstractAspectJAdvice implements MyAdvice{ private Method aspectMethod; private Object aspectTarget; public MyAbstractAspectJAdvice(Method aspectMethod, Object aspectTarget) { this.aspectMethod = aspectMethod; this.aspectTarget = aspectTarget; } //反射动态调用方法 protected Object invokeAdviceMethod(MyJoinPoint joinPoint,Object returnValue,Throwable ex)throws Throwable{ Class<?>[] paramsTypes = this.aspectMethod.getParameterTypes(); if(null == paramsTypes || paramsTypes.length == 0){ return this.aspectMethod.invoke(aspectTarget); } else { Object[] args = new Object[paramsTypes.length]; for (int i = 0; i < paramsTypes.length; i++) { if(paramsTypes[i] == MyJoinPoint.class){ args[i] = joinPoint; }else if(paramsTypes[i] == Throwable.class){ args[i] = ex; }else if(paramsTypes[i] == Object.class){ args[i] = returnValue; } } return this.aspectMethod.invoke(aspectTarget,args); } } }
-
MethodBeforeAdvice:继承自AbstractAspectJAdivce,实现Advice和MethodInterceptor接口,在invoke()中控制前置通知的调用顺序
package com.czh.spring.framework.aop.aspect; import com.czh.spring.framework.aop.intercept.MyMethodInterceptor; import com.czh.spring.framework.aop.intercept.MyMethodInvocation; import java.lang.reflect.Method; /** * 前置通知具体实现 */ public class MyMethodBeforeAdvice extends MyAbstractAspectJAdvice implements MyAdvice, MyMethodInterceptor { private MyJoinPoint joinPoint; public MyMethodBeforeAdvice(Method aspectMethod, Object aspectTarget) { super(aspectMethod, aspectTarget); } public void before(Method method,Object[] args,Object target)throws Throwable{ invokeAdviceMethod(this.joinPoint,null,null); } @Override public Object invoke(MyMethodInvocation mi) throws Throwable { this.joinPoint = mi; this.before(mi.getMethod(), mi.getArguments(), mi.getThis()); return mi.proceed(); } }
-
AfterReturningAdvice:继承自AbstractAspectJAdivce,实现Advice和MethodInterceptor接口,在invoke()中控制后置通知的调用顺序
package com.czh.spring.framework.aop.aspect; import com.czh.spring.framework.aop.intercept.MyMethodInterceptor; import com.czh.spring.framework.aop.intercept.MyMethodInvocation; import java.lang.reflect.Method; public class MyAfterReturningAdvice extends MyAbstractAspectJAdvice implements MyAdvice, MyMethodInterceptor{ private MyJoinPoint joinPoint; public MyAfterReturningAdvice(Method aspectMethod, Object aspectTarget) { super(aspectMethod, aspectTarget); } @Override public Object invoke(MyMethodInvocation mi) throws Throwable { Object retVal = mi.proceed(); this.joinPoint = mi; this.afterReturning(retVal,mi.getMethod(),mi.getArguments(),mi.getThis()); return retVal; } private void afterReturning(Object retVal, Method method, Object[] arguments, Object aThis) throws Throwable { invokeAdviceMethod(joinPoint,retVal,null); } }
-
AfterThrowingAdvice:继承自AbstractAspectJAdivce,实现Advice和MethodInterceptor接口,在invoke()中控制异常通知的调用顺序
package com.czh.spring.framework.aop.aspect; import com.czh.spring.framework.aop.intercept.MyMethodInterceptor; import com.czh.spring.framework.aop.intercept.MyMethodInvocation; import java.lang.reflect.Method; /** * 异常通知的具体实现 */ public class MyAfterThrowingAdvice extends MyAbstractAspectJAdvice implements MyAdvice, MyMethodInterceptor { private String throwingName; private MyMethodInvocation mi; public MyAfterThrowingAdvice(Method aspectMethod, Object aspectTarget) { super(aspectMethod, aspectTarget); } public void setThrowingName(String aspectAfterThrowingName) { this.throwingName = aspectAfterThrowingName; } @Override public Object invoke(MyMethodInvocation mi) throws Throwable { try { return mi.proceed(); }catch (Throwable ex){ invokeAdviceMethod(mi,null,ex.getCause()); throw ex; } } }
接入getBean方法
-
如何集成到IoC容器中去?在MyApplicationContext中的instantiateBean()方法,在初始化时就可以确定是否返回原生Bean或者ProxyBean。
private Object instantiateBean(MyBeanDefinition beanDefinition) { Object instance = null; String beanClassName = beanDefinition.getBeanClassName(); try { //因为根据Class才能确定一个类是否有实例 if(this.factoryBeanObjectCache.containsKey(beanClassName)){ instance = this.factoryBeanObjectCache.get(beanClassName); }else { Class<?> clazz = Class.forName(beanClassName); instance = clazz.newInstance(); MyAdvisedSupport config = instantionAopConfig(beanDefinition); config.setTargetClass(clazz); config.setTarget(instance); if(config.pointCutMatch()){ instance = createProxy(config).getProxy(); } this.factoryBeanObjectCache.put(beanClassName,instance); this.factoryBeanObjectCache.put(beanDefinition.getFactoryBeanName(),instance); } return instance; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } private MyAdvisedSupport instantionAopConfig(MyBeanDefinition beanDefinition) { MyAopConfig config = new MyAopConfig(); config.setPointCut(reader.getConfig().getProperty("pointCut")); config.setAspectClass(reader.getConfig().getProperty("aspectClass")); config.setAspectBefore(reader.getConfig().getProperty("aspectBefore")); config.setAspectAfter(reader.getConfig().getProperty("aspectAfter")); config.setAspectAfterThrow(reader.getConfig().getProperty("aspectAfterThrow")); config.setAspectAfterThrowingName(reader.getConfig().getProperty("aspectAfterThrowingName")); return new MyAdvisedSupport(config); } private MyAopProxy createProxy(MyAdvisedSupport config){ Class targetClass = config.getTargetClass(); if(targetClass.getInterfaces().length>0){ return new MyJdkDynamicAopProxy(config); } return new MyCglibAopProxy(config); }
织入业务代码
-
创建LogAspect类,实现对业务方法的监控。主要是记录目标方法的调用日志,获取目标方法名、实参列表、每次调用所消耗的时间
package com.czh.spring.demo.aspect; import com.czh.spring.framework.aop.aspect.MyJoinPoint; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; /** * 定义一个织入的切面逻辑,也就是要针对目标代理对象增强的逻辑 * 本类主要完成对方法调用的监控,监听目标方法每次执行所消耗的时间 */ @Slf4j public class LogAspect { //在调用一个方法之前,执行before方法 public void before(MyJoinPoint joinPoint){ joinPoint.setUserAttribute("startTime_" + joinPoint.getMethod().getName(),System.currentTimeMillis()); //这个方法中的逻辑,是由我们自己写的 log.info("Invoker Before Method!!!" + "\nTargetObject:" + joinPoint.getThis() + "\nArgs:" + Arrays.toString(joinPoint.getArguments())); } //在调用一个方法之后,执行after方法 public void after(MyJoinPoint joinPoint){ log.info("Invoker After Method!!!" + "\nTargetObject:" + joinPoint.getThis() + "\nArgs:" + Arrays.toString(joinPoint.getArguments())); long startTime = (Long) joinPoint.getUserAttribute("startTime_" + joinPoint.getMethod().getName()); long endTime = System.currentTimeMillis(); System.out.println("use time :" + (endTime - startTime)); } public void afterThrowing(MyJoinPoint joinPoint, Throwable ex){ log.info("出现异常" + "\nTargetObject:" + joinPoint.getThis() + "\nArgs:" + Arrays.toString(joinPoint.getArguments()) + "\nThrows:" + ex.getMessage()); } }
-
修改下ModifyService,在里面故意抛出异常测试
package com.czh.spring.demo.service.impl; import com.czh.spring.demo.service.ModifyService; public class ModifyServiceImpl implements ModifyService { /** * 增加 */ @Override public String add(String name, String address) throws Exception { throw new Exception("故意抛出异常,测试前面通知是否生效!!!"); // return "modifyService add,name=" + name + ",address=" + address; } /** * 修改 */ @Override public String edit(Integer id, String name) { return "modifyService edit,id=" + id + ",name=" + name; } /** * 删除 */ @Override public String remove(Integer id) { return "modifyService id=" + id; } }
测试
-
http://localhost:8080/luelueking_Spring_Source_war/web/add.json以及控制台输出
-
控制台输出
-
http://localhost:8080/luelueking_Spring_Source_war/web/query.json?name=luelueking
-
控制台输出