手撸Spring系列10:Spring AOP(实战篇)
说在前头: 笔者本人为大三在读学生,书写文章的目的是为了对自己掌握的知识和技术进行一定的记录,同时乐于与大家一起分享,因本人资历尚浅,发布的文章难免存在一些错漏之处,还请阅读此文章的大牛们见谅与斧正。若在阅读时有任何的问题,也可通过评论提出,本人将根据自身能力对问题进行一定的解答。
手撸Spring系列是笔者本人首次尝试的、较为规范的系列博客,将会围绕Spring框架分为
IOC/DI 思想
、Spring MVC
、AOP 思想
、Spring JDBC
四个模块,并且每个模块都会分为理论篇
、源码篇
、实战篇
三个篇章进行讲解(大约12篇文章左右的篇幅)。从原理出发,深入浅出,一步步接触Spring源码并手把手带领大家一起写一个 迷你版的Spring框架 ,促进大家进一步了解Spring的本质!由于源码篇涉及到源码的阅读,可能有小伙伴没有成功构建好Spring源码的阅读环境,笔者强烈建议:想要真正了解Spring,一定要构建好源码的阅读环境再进行研究,具体构建过程可查看笔者此前的博客:《如何构建Spring5源码阅读环境》
前言
经过前面AOP
的理论篇和源码篇,我们又再次进入到了实战篇,此次的实战篇也是建立在之前IOC/DI实战篇的代码基础之上的,因此在进入到AOP
实战篇之前,请确保自己已经成功实现了IOC
和DI
功能~!!
还是老规矩,在正式开始编写实战篇的代码前,还是先让各位读者朋友们瞅瞅整个程序的架构。(AOP
功能相对于之前我们实现的IOC
、DI
、MVC
都要复杂很多,也是我们手撸Spring系列
最难的模块,因此需要手写的类会比前面章节的实战篇多一些)
代码仓库地址:https://gitee.com/bosen-once/mini-spring
一、注解类的编写
还是一样的,我们实战篇的代码从最简单的注解类开始着手编写,除了之前IOC需要的注解外,我们还需要再编写与AOP相关的注解:
- ①
After
:后置通知注解类 - ②
AfterReturning
:后置返回通知注解类 - ③
AfterThrowing
:异常通知注解类 - ④
Aspect
:切面注解类 - ⑤
Before
:前置通知注解类 - ⑥
Pointcut
:切点注解类
①After:后置通知注解类
package org.springframework.annotation.aop;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** <p>后置通知注解类</p>* @author Bosen* @date 2021/9/19 15:52*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {String value() default "";
}
②AfterReturning:后置返回通知注解类
package org.springframework.annotation.aop;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** <p>后置返回通知注解类</p>* @author Bosen* @date 2021/9/19 15:53*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterReturning {String value() default "";
}
③AfterThrowing:异常通知注解类
package org.springframework.annotation.aop;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** <p>异常通知注解类</p>* @author Bosen* @date 2021/9/19 15:54*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {String value() default "";
}
④Aspect:切面注解类
package org.springframework.annotation.aop;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** <p>切面注解类</p>* @author Bosen* @date 2021/9/19 15:50*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {String value() default "";
}
⑤Before:前置通知注解类
package org.springframework.annotation.aop;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** <p>前置通知注解类</p>* @author Bosen* @date 2021/9/19 15:52*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {String value() default "";
}
⑥Pointcut:切点注解类
package org.springframework.annotation.aop;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** <p>切点注解类</p>* @author Bosen* @date 2021/9/19 15:51*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {String value() default "";
}
二、接口类的编写
主要编写回调通知接口、连接点接口、回调方法拦截器接口、代理工厂接口:
Advice
:回调通知定义接口JoinPoint
:连接点接口AopProxy
:代理工厂接口MethodInterceptor
:方法拦截器接口
1.Advice:回调通知定义接口
package org.springframework.aop.aspect;/*** <p>回调通知接口</p>* @author Bosen* @date 2021/9/19 21:40*/
public interface Advice {}
2.JoinPoint:连接点接口
package org.springframework.aop.aspect;import java.lang.reflect.Method;/*** <p>连接点接口,定义切点的抽象</p>* @author Bosen* @date 2021/9/19 16:10*/
public interface JoinPoint {/*** <p>业务方法本身</p>*/Method getMethod();/*** <p>该方法的参数列表</p>*/Object[] getArguments();/*** <p>该方法对应的对象</p>*/Object getThis();/*** <p>在joinPoint中添加自定义属性</p>*/void setUserAttribute(String key, Object value);/*** <p>获取自定义属性</p>*/Object getUserAttribute(String key);
}
3.AopProxy:代理工厂接口
package org.springframework.aop;/*** <p>代理工厂接口</p>* @author Bosen* @date 2021/9/19 22:51*/
public interface AopProxy {Object getProxy();Object getProxy(ClassLoader classLoader);
}
4.MethodInterceptor:方法拦截器接口
package org.springframework.aop.intercept;/*** <p>方法拦截器接口</p>* @author Bosen* @date 2021/9/19 16:38*/
public interface MethodInterceptor {Object invoke(MethodInvocation mi) throws Exception;
}
三、配置类的编写
笔者要实现的AOP的功能是基于注解作为配置的,因此,我们在IOC容器初始化时就应该对有AOP注解标记类进行扫描,将与切面相关的信息存储起来,
AopConfig
:存储切面信息(一个切面对应一个AopConfig)AdvisedSupport
:完成对切面类的扫描
1.AopConfig
package org.springframework.aop;/*** <p>AOP配置的封装对象</p>* @author Bosen* @date 2021/9/19 16:08*/
public class AopConfig {/*** <p>切点</p>*/private String pointCut;/*** <p>前置通知</p>*/private String before;/*** <p>后置通知</p>*/private String afterReturn;/*** <p>异常通知</p>*/private String afterThrow;/*** <p>异常类型</p>*/private String afterThrowClass;/*** <p>切面类</p>*/private String aspectClass;public String getPointCut() {return pointCut;}public void setPointCut(String pointCut) {this.pointCut = pointCut;}public String getBefore() {return before;}public void setBefore(String before) {this.before = before;}public String getAfterReturn() {return afterReturn;}public void setAfterReturn(String afterReturn) {this.afterReturn = afterReturn;}public String getAfterThrow() {return afterThrow;}public void setAfterThrow(String afterThrow) {this.afterThrow = afterThrow;}public String getAfterThrowClass() {return afterThrowClass;}public void setAfterThrowClass(String afterThrowClass) {this.afterThrowClass = afterThrowClass;}public String getAspectClass() {return aspectClass;}public void setAspectClass(String aspectClass) {this.aspectClass = aspectClass;}
}
2.AdvisedSupport
package org.springframework.aop.support;import org.springframework.aop.AopConfig;
import org.springframework.aop.aspect.AfterReturningAdvice;
import org.springframework.aop.aspect.MethodBeforeAdvice;
import org.springframework.aop.aspect.AfterThrowingAdvice;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;/*** <p>解析AOP配置</p>* @author Bosen* @date 2021/9/19 16:56*/
public class AdvisedSupport {private Class targetClass;private Object target;private Pattern pointCutClassPattern;private transient Map<Method, List<Object>> methodCache = new HashMap<>();private AopConfig config;public AdvisedSupport(AopConfig config) {this.config = config;}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 = this.methodCache.get(method);// 缓存未命中if (cached == null) {Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());cached = this.methodCache.get(m);this.methodCache.put(m, cached);}return cached;}public boolean pointCutMatch() {return this.pointCutClassPattern.matcher(this.targetClass.toString()).matches();}private void parse() {String pointcut = config.getPointCut().replaceAll("\\.", "\\\\.").replaceAll("\\*", "\\.\\*").replaceAll("\\(", "\\\\(").replaceAll("\\)", "\\\\)");String pointCutForClass = pointcut.substring(0, pointcut.lastIndexOf("\\(") - 4);pointCutClassPattern = Pattern.compile("class " + pointCutForClass.substring(pointCutForClass.lastIndexOf(" ") + 1));Pattern pattern = Pattern.compile(pointCutForClass);try {// 获取切面类Class aspectClass = Class.forName(config.getAspectClass());Map<String, Method> aspectMethods = new HashMap<>();// 获取切面类的通知方法for (Method method : aspectClass.getMethods()) {aspectMethods.put(method.getName(), method);}// 获取当前对象的方法for (Method method : targetClass.getMethods()) {String methodString = method.toString();// 去掉方法头中throws及其往后的字符if (methodString.contains("throws")) {methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim();}Matcher matcher = pattern.matcher(methodString);if (matcher.matches()) {// 匹配成功List<Object> advices = new LinkedList<>();// 前置通知if (!(config.getBefore() == null || "".equals(config.getBefore()))) {advices.add(new MethodBeforeAdvice(aspectMethods.get(config.getBefore()), aspectClass.newInstance()));}// 后置返回通知if (!(config.getAfterReturn() == null || "".equals(config.getAfterReturn()))) {advices.add(new AfterReturningAdvice(aspectMethods.get(config.getAfterReturn()), aspectClass.newInstance()));}// 异常通知if (!(config.getAfterThrow() == null || "".equals(config.getAfterThrow()))) {AfterThrowingAdvice afterThrowingAdvice =new AfterThrowingAdvice(aspectMethods.get(config.getAfterThrow()), aspectClass.newInstance());afterThrowingAdvice.setThrowingName(config.getAfterThrowClass());advices.add(afterThrowingAdvice);}this.methodCache.put(method, advices);}}} catch (Exception e) {e.printStackTrace();}}
}
四、拦截器的编写
通知拦截器有前置、后置和异常通知拦截器,除了这三个拦截器外,他们共同还拥有一个父类,用于处理三个拦截器通用的逻辑任务。
AbstractAspectJAdvice
:通知拦截器父类(用于处理子类的通用逻辑任务)MethodBeforeAdvice
:前置通知拦截器AfterReturningAdvice
:后置通知拦截器AfterThrowingAdvice
:异常通知拦截器MethodInvocation
:通知拦截器执行链
1.AbstractAspectJAdvice
package org.springframework.aop.aspect;import java.lang.reflect.Method;/*** <p>定义回调通知的通用逻辑</p>* @author Bosen* @date 2021/9/19 21:41*/
public abstract class AbstractAspectJAdvice {private Method aspectMethod;private Object aspectTarget;public AbstractAspectJAdvice(Method aspectMethod, Object aspectTarget) {this.aspectMethod = aspectMethod;this.aspectTarget = aspectTarget;}protected Object invokeAdviceMethod(JoinPoint joinPoint, Object returnValue, Throwable ex) throws Exception {Class<?>[] paramsTypes = this.aspectMethod.getParameterTypes();if (paramsTypes.length == 0) {return this.aspectMethod.invoke(aspectTarget);}Object[] args = new Object[paramsTypes.length];for (int i = 0; i < paramsTypes.length; i++) {if (paramsTypes[i] == JoinPoint.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);}public Method getAspectMethod() {return aspectMethod;}public void setAspectMethod(Method aspectMethod) {this.aspectMethod = aspectMethod;}public Object getAspectTarget() {return aspectTarget;}public void setAspectTarget(Object aspectTarget) {this.aspectTarget = aspectTarget;}
}
2.MethodBeforeAdvice
package org.springframework.aop.aspect;import org.springframework.aop.intercept.MethodInterceptor;
import org.springframework.aop.intercept.MethodInvocation;import java.lang.reflect.Method;/*** <p>前置通知</p>* @author Bosen* @date 2021/9/19 21:38*/
public class MethodBeforeAdvice extends AbstractAspectJAdvice implements Advice, MethodInterceptor {public MethodBeforeAdvice(Method aspectMethod, Object aspectTarget) {super(aspectMethod, aspectTarget);}@Overridepublic Object invoke(MethodInvocation mi) throws Exception {super.invokeAdviceMethod(mi, null, null);return mi.proceed();}
}
3.AfterReturningAdvice
package org.springframework.aop.aspect;import org.springframework.aop.intercept.MethodInterceptor;
import org.springframework.aop.intercept.MethodInvocation;import java.lang.reflect.Method;/*** <p>后置通知</p>* @author Bosen* @date 2021/9/19 22:18*/
public class AfterReturningAdvice extends AbstractAspectJAdvice implements Advice, MethodInterceptor {public AfterReturningAdvice(Method aspectMethod, Object aspectTarget) {super(aspectMethod, aspectTarget);}@Overridepublic Object invoke(MethodInvocation mi) throws Exception {Object returnValue = mi.proceed();invokeAdviceMethod(mi, returnValue, null);return returnValue;}
}
4.AfterThrowingAdvice
package org.springframework.aop.aspect;import org.springframework.aop.intercept.MethodInterceptor;
import org.springframework.aop.intercept.MethodInvocation;import java.lang.reflect.Method;/*** <p>异常通知</p>* @author Bosen* @date 2021/9/19 22:17*/
public class AfterThrowingAdvice extends AbstractAspectJAdvice implements Advice, MethodInterceptor {private String throwingName;private MethodInvocation mi;public AfterThrowingAdvice(Method aspectMethod, Object aspectTarget) {super(aspectMethod, aspectTarget);}public void setThrowingName(String name) {this.throwingName = name;}@Overridepublic Object invoke(MethodInvocation mi) throws Exception {try {return mi.proceed();} catch (Throwable ex) {super.invokeAdviceMethod(mi, null, ex.getCause());throw ex;}}
}
5.MethodInvocation
package org.springframework.aop.intercept;import org.springframework.aop.aspect.JoinPoint;import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** <p>执行拦截器链</p>* @author Bosen* @date 2021/9/19 16:15*/
public class MethodInvocation implements JoinPoint {/*** <p>代理对象</p>*/private Object proxy;/*** <p>代理的目标方法</p>*/private Method method;/*** <p>代理的目标对象</p>*/private Object target;/*** <p>代理的目标类</p>*/private Class<?> targetClass;/*** <p>代理的方法的参数列表</p>*/private Object[] arguments;/*** <p>回调方法链</p>*/private List<Object> interceptorsAndDynamicMethodMatchers;/*** <p>保存自定义属性</p>*/private Map<String, Object> userAttributes;private int currentInterceptor = -1;public MethodInvocation(Object proxy, Method method, Object target, Class<?> targetClass, Object[] arguments,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 Exception {// 如果执行链执行完后,执行joinPoint自己的业务逻辑方法if (this.currentInterceptor == this.interceptorsAndDynamicMethodMatchers.size() -1) {return this.method.invoke(this.target, this.arguments);}Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptor);// 如果该对象属于拦截器if (interceptorOrInterceptionAdvice instanceof MethodInterceptor) {MethodInterceptor mi = (MethodInterceptor) interceptorOrInterceptionAdvice;return mi.invoke(this);}return proceed();}@Overridepublic Method getMethod() {return this.method;}@Overridepublic Object[] getArguments() {return this.arguments;}@Overridepublic Object getThis() {return this.target;}@Overridepublic void setUserAttribute(String key, Object value) {if (value != null) {if (this.userAttributes == null) {this.userAttributes = new HashMap<>();}this.userAttributes.put(key, value);} else {if (this.userAttributes != null) {this.userAttributes.remove(key);}}}@Overridepublic Object getUserAttribute(String key) {return this.userAttributes != null ? this.userAttributes.get(key) : null;}
}
五、CGLib和JDK动态代理
如果需要代理的类实现了接口,默认使用JDK
动态代理,否则使用CGLib
代理
1.JdkDynamicAopProxy
package org.springframework.aop;import org.springframework.aop.intercept.MethodInvocation;
import org.springframework.aop.support.AdvisedSupport;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;/*** <p>JDK动态代理</p>* @author Bosen* @date 2021/9/19 22:58*/
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {private Class targetClass;private Object target;private AdvisedSupport config;public JdkDynamicAopProxy(AdvisedSupport config) {this.config = config;}@Overridepublic Object getProxy() {return getProxy(this.config.getTargetClass().getClassLoader());}@Overridepublic Object getProxy(ClassLoader classLoader) {this.targetClass = this.config.getTargetClass();this.target = this.config.getTarget();return Proxy.newProxyInstance(classLoader, this.config.getTargetClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Exception {List<Object> interceptorsAndDynamicMethodMatchers =this.config.getInterceptorAndDynamicInterceptionAdvice(method, this.targetClass);MethodInvocation invocation =new MethodInvocation(proxy, method, this.target,this.targetClass, args, interceptorsAndDynamicMethodMatchers);return invocation.proceed();}
}
2.CglibAopProxy
package org.springframework.aop;import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.springframework.aop.intercept.MethodInvocation;
import org.springframework.aop.support.AdvisedSupport;import java.lang.reflect.Method;
import java.util.List;/*** <p>CGLIG代理</p>* @author Bosen* @date 2021/9/19 22:53*/
public class CglibAopProxy implements AopProxy, MethodInterceptor {private Class targetClass;private Object target;private AdvisedSupport config;public CglibAopProxy(AdvisedSupport config) {this.config = config;}@Overridepublic Object getProxy() {return getProxy(this.config.getTargetClass().getClassLoader());}@Overridepublic Object getProxy(ClassLoader classLoader) {this.targetClass = this.config.getTargetClass();this.target = this.config.getTarget();Enhancer enhancer = new Enhancer();enhancer.setSuperclass(this.targetClass);enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Exception {List<Object> interceptorsAndDynamicMethodMatchers =this.config.getInterceptorAndDynamicInterceptionAdvice(method, this.targetClass);MethodInvocation invocation =new MethodInvocation(proxy, method, this.target,this.targetClass, args, interceptorsAndDynamicMethodMatchers);return invocation.proceed();}
}
六、工具类的编写
工具类AopUtils
主要完成的任务如下:
- 存储切面配置信息: 使用集合存储扫描出来的切面配置
- 初始化AOP配置类: 启动扫描有AOP注解标记的类
- 创建代理类: 为被代理的对象创建对应的代理类
- 判断是否是代理类: 判断传入来的对象是否是代理类
- 获取被代理的对象: 由代理对象获取被代理的对象
AopUtils
package org.springframework.aop.util;import org.springframework.annotation.aop.*;
import org.springframework.aop.AopConfig;
import org.springframework.aop.AopProxy;
import org.springframework.aop.CglibAopProxy;
import org.springframework.aop.JdkDynamicAopProxy;
import org.springframework.aop.support.AdvisedSupport;
import org.springframework.beans.factory.config.BeanDefinition;import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;/*** <p>Aop工具类</p>* @author Bosen* @date 2021/9/19 23:49*/
public class AopUtils {/*** <p>存储切面配置信息</p>*/public static final List<AdvisedSupport> CONFIGS = new ArrayList<>();/*** <p>初始化AOP配置类</p>*/public static void instantiationAopConfig(List<BeanDefinition> beanDefinitions) throws Exception {for (BeanDefinition beanDefinition : beanDefinitions) {Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());// 如果该类不是切面Aspect则跳过if (!clazz.isAnnotationPresent(Aspect.class)) {continue;}AopConfig config = new AopConfig();Method[] methods = clazz.getMethods();// 设置切点和回调方法for (Method method : methods) {if (method.isAnnotationPresent(Pointcut.class)) {// 设置切点config.setPointCut(method.getAnnotation(Pointcut.class).value());}else if (method.isAnnotationPresent(Before.class)) {// 前后方法config.setBefore(method.getName());}else if (method.isAnnotationPresent(AfterReturning.class)) {// 后置方法config.setAfterReturn(method.getName());}else if (method.isAnnotationPresent(AfterThrowing.class)) {// 异常方法config.setAfterThrow(method.getName());config.setAfterThrowClass("java.lang.Exception");}}// 没有设置切点,跳过if (config.getPointCut() == null) {continue;}config.setAspectClass(beanDefinition.getBeanClassName());CONFIGS.add(new AdvisedSupport(config));}}/*** <p>创建代理类</p>*/public static AopProxy createProxy(AdvisedSupport config) {Class targetClass = config.getTargetClass();// 如果实现了接口则使用jdk动态代理,否则使用Cglib代理if (targetClass.getInterfaces().length > 0) {return new JdkDynamicAopProxy(config);}// 使用CGLIB代理return new CglibAopProxy(config);}/*** <p>判断是否是代理类</p>*/public static boolean isAopProxy(Object object) {return object.getClass().getSimpleName().contains("$");}/*** <p>获取被代理的对象</p>*/public static Object getTarget(Object proxy) throws Exception {if(proxy.getClass().getSuperclass() == Proxy.class) {return getJdkDynamicProxyTargetObject(proxy);} else {return getCglibProxyTargetObject(proxy);}}/*** <p>获取CGLIB被代理的对象</p>*/private static Object getCglibProxyTargetObject(Object proxy) throws Exception {Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");h.setAccessible(true);Object dynamicAdvisedInterceptor = h.get(proxy);Field config = dynamicAdvisedInterceptor.getClass().getDeclaredField("config");config.setAccessible(true);return ((AdvisedSupport) config.get(dynamicAdvisedInterceptor)).getTarget();}/*** <p>获取jdk代理被代理对象</p>*/private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {Field h = proxy.getClass().getSuperclass().getDeclaredField("h");h.setAccessible(true);Object aopProxy = h.get(proxy);Field config = aopProxy.getClass().getDeclaredField("config");config.setAccessible(true);return ((AdvisedSupport) config.get(aopProxy)).getTarget();}
}
七、IOC容器接入AOP功能
为了让我们写好的AOP功能接入IOC容器中,我们需要对IOC的代码进行一定的修改:
- IOC容器初始化时,初始化AOP配置信息。
- 实例化Bean时,判断该Bean是否是需要被代理的类,如果是,则使用代理类代替其存入IOC容器中
- 依赖注入
1.初始化AOP配置
修改refresh
方法如下:
@Override
public void refresh() throws Exception {// 扫描需要扫描的包,并把相关的类转化为beanDefinitionList<BeanDefinition> beanDefinitions = reader.loadBeanDefinitions();// 注册,将beanDefinition放入IOC容器存储doRegisterBeanDefinition(beanDefinitions);// 初始化AOP配置类AopUtils.instantiationAopConfig(beanDefinitions);// 将非懒加载的类初始化doAutowired();
}
2.将代理类存入IOC容器
修改instantiateBean
方法如下:
private Object instantiateBean(BeanDefinition beanDefinition) {Object instance = null;String className = beanDefinition.getBeanClassName();try {// 先判断单例池中是否存在该类的实例if (this.factoryBeanObjectCache.containsKey(className)) {instance = this.factoryBeanObjectCache.get(className);} else {Class<?> clazz = Class.forName(className);instance = clazz.newInstance();// 接入AOP功能for (AdvisedSupport aspect : AopUtils.CONFIGS) {aspect.setTargetClass(clazz);aspect.setTarget(instance);if (aspect.pointCutMatch()) {instance = AopUtils.createProxy(aspect).getProxy();}}this.factoryBeanObjectCache.put(className, instance);this.factoryBeanObjectCache.put(beanDefinition.getFactoryBeanName(), instance);}} catch (Exception e) {e.printStackTrace();}return instance;
}
3.依赖注入
修改populateBean
方法如下:
public void populateBean(Object instance) throws Exception {// 判断是否是代理类if (AopUtils.isAopProxy(instance)) {instance = AopUtils.getTarget(instance);}Class clazz = instance.getClass();// 判断是否有Controller、ITestService、Component、Repository等注解标记if (!(clazz.isAnnotationPresent(Component.class) ||clazz.isAnnotationPresent(Controller.class) ||clazz.isAnnotationPresent(Service.class) ||clazz.isAnnotationPresent(Repository.class))) {return;}Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {// 如果属性没有被Autowired标记,则跳过if (!field.isAnnotationPresent(Autowired.class)) {continue;}String autowiredBeanName = field.getType().getName();field.setAccessible(true);try {field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());} catch (IllegalAccessException e) {e.printStackTrace();}}
}
八、AOP功能测试
1.LogAspect切面类
定义切点,前置、后置、异常通知
package org.springframework.test.aspect;import org.springframework.annotation.aop.*;@Aspect
public class LogAspect {/*** <p>配置切面</p>*/@Pointcut("public * org.springframework.test.service.*.*(..)")public void logPointcut(){}/*** <p>前置通知</p>*/@Beforepublic void logBefore() {System.out.println("This is LogAspect before");}/*** <p>后置返回通知</p>*/@AfterReturningpublic void logAfter() {System.out.println("This is LogAspect After");}/*** <p>异常通知</p>*/@AfterThrowingpublic void logAfterThrowing() {System.out.println("This is LogAspect AfterThrowing");}
}
2.ApplicationConfig容器配置类
IOC模块的配置类(基于注解配置)
package org.springframework.test.config;import org.springframework.annotation.core.ComponentScan;/*** <p>配置类</p>* @author Bosen* @date 2021/9/11 14:11*/
@ComponentScan("org.springframework.test")
public class ApplicationConfig {}
3.TestDAO层类
用于模拟访问数据库的操作
package org.springframework.test.dao;import org.springframework.annotation.core.Repository;/*** @author Bosen* @date 2021/9/11 22:29*/
@Repository
public class TestDAO {public String echo() {return "This is TestDAO#echo!!!";}
}
4.IService层接口
用于测试jdk动态代理,其中定义了echo
方法
package org.springframework.test.service;/*** <p>用于测试AOP JDK动态代理的接口</p>* @author Bosen* @date 2021/9/21 21:05*/
public interface IService {void echo();
}
5.TestService实现类
package org.springframework.test.service;import org.springframework.annotation.beans.Autowired;
import org.springframework.annotation.core.Service;
import org.springframework.test.dao.TestDAO;/*** @author Bosen* @date 2021/9/11 22:30*/
@Service
public class TestService implements IService {@AutowiredTestDAO testDAO;@Overridepublic void echo() {System.out.println(testDAO.echo());}
}
6.编写测试类
package org.springframework.test;import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.config.ApplicationConfig;
import org.springframework.test.service.IService;/*** <p>测试启动类</p>* @author Bosen* @date 2021/9/12 0:25*/
public class ApplicationTest {public static void main(String[] args) throws Exception {ApplicationContext applicationContext =new AnnotationConfigApplicationContext(ApplicationConfig.class);IService service = (IService) applicationContext.getBean("testService");service.echo();}
}
执行main
方法,运行结果如下
此时,在运行echo
的前后会分别执行前置通知和后置通知。接下来我们尝试在echo
方法中故意抛出一个异常,再次执行main
方法,运行结果如下
此时,在echo
方法前仍会执行前置通知,但是并不会执行后置通知,被取而代之的运行了异常通知。
至此,迷你版Spirng AOP功能我们已经亲手成功实现~!!
手撸Spring系列10:Spring AOP(实战篇)相关推荐
- QCC304x系列开发教程(实战篇) 之10.2 QCC3040之教你调试入仓和出仓情景下的程序运行
查看全部教程开发请点击:高通蓝牙耳机QCC304x开发详解汇总(持续更新中) 查看本文全部文章请点击:QCC304x系列开发教程(实战篇) 之10.2 QCC3040之教你调试入仓和出仓情景下的程序运 ...
- QCC304x系列开发教程(实战篇) 之 QCC3040之RF测试
查看全部教程开发请点击:高通蓝牙耳机QCC304x开发详解汇总(持续更新中) 查看本文全部文章请点击:QCC304x系列开发教程(实战篇) 之 QCC3040之RF测试 更新记录链接:QCC514x- ...
- QCC304x系列开发教程(实战篇) 之 QCC304x之DFU(固件升级)
高通蓝牙耳机QCC304x开发详解汇总(持续更新中) 查看全部文章地址QCC304x系列开发教程(实战篇) 之 QCC304x之DFU(固件升级)_心跳包的博客-CSDN博客 版权归作者所有,未经允许 ...
- QCC304x系列开发教程(实战篇) 之5.3 QCC3040之QACT用户指南
高通蓝牙耳机QCC304x开发详解汇总(持续更新中) 查看全部文章地址QCC304x系列开发教程(实战篇) 之5.3 QCC3040之QACT用户指南_心跳包的博客-CSDN博客 版权归作者所有,未 ...
- QCC304x系列开发教程(实战篇)之4.2QCC3040之MDE按键导入配置
查看全部教程开发请点击:高通蓝牙耳机QCC304x开发详解汇总(持续更新中) 查看本文全部文章请点击:QCC304x系列开发教程(实战篇)之4.2QCC3040之MDE按键导入配置 ========= ...
- QCC304x系列开发教程(实战篇)之5.2 QCC3040之提示音
查看全部教程开发请点击:高通蓝牙耳机QCC304x开发详解汇总(持续更新中) 查看本文全部文章请点击:QCC304x系列开发教程(实战篇)之5.2 QCC3040之提示音 更新记录链接:QCC514x ...
- [Spring手撸专栏学习笔记]——把AOP动态代理,融入到Bean的生命周期
本文是学习<Spring 手撸专栏>第 10 章笔记,主要记录我的一些debug调试过程,方便后期复习.具体学习,大家可以去看一下这个专栏,强烈推荐. 方案 其实在有了AOP的核心功能实现 ...
- 【Spring 系列】Spring知识地图
文章目录 Spring IOC 知道 会用 熟练 掌握 专家 Spring AOP 知道 会用 熟练 掌握 专家 Spring MVC 知道 会用 熟练 掌握 专家 Spring WebFlux 知道 ...
- Spring系列之Spring框架和SpringAOP集成过程分析(十)
转载请注明出处:https://blog.csdn.net/zknxx/article/details/80724180 在开始这个系列之前大家先想一下我们是怎么在项目中使用SpringAOP的(这里 ...
最新文章
- 全自动驾驶“生死时速”,特斯拉收购计算机视觉创企DeepScale
- TensorFlow 笔记6--迁移学习
- PHP Hashtable实现源码分析
- String s1==s2面试题
- 51nod1245 Binomial Coefficients Revenge
- Maven—Eclipse中配置Maven
- Vue2.0安装教程
- git-remote-https.exe-无法找到入口
- (CVPR-2022)AdaViT:用于高效图像识别的自适应视觉变换器
- 塞班S60V5上的画图工具
- 人工智能中的顶级期刊
- CHIL-SQL-NULL 函数
- javaeye恢复正常了
- 科视Christie 举行中国分销商授证仪式
- matlab中wav转txt6,WAV转TXT专家下载
- Word分页符和分节符区别
- JS_自己写的JQ插件
- x38和x48是服务器芯片吗,guoshuo X58是什么主板
- 我的知识星球为什么要设置为6000元/年?
- UMP系统功能 容灾
热门文章
- CUDA + Visual Studio 环境搭建
- SQL中NVL()用法
- MySql项目查询优化经验总结
- char *c和char c[]区别
- element el-table 表格行列合并[{class1:‘1101‘,arr1:[1,2,3,5],class2:‘1102‘,arr2:[4,5,6],name:‘h‘}]
- 2018网易校招移动安全工程师笔试题
- Redis如何应对并发访问
- 整除java符号_Java运算符
- C++实现字符串的部分复制
- 通过sql给表添加字段