Spring之AOP底层源码解析
Spring之AOP底层源码解析
1、动态代理
代理模式的解释:为其他对象提供一种代理以控制对这个对象的访问,增强一个类中的某个方法,对程序进行扩展。
举个例子
public class UserService {public void test() {System.out.println("test...");}
}
此时,我们 new 一个 UserService 对象,然后执行 test()
方法,结果是显而易见的。
那如果我们现在想在不修改 UserService 类的源码前提下,给 test()
方法增加额外的逻辑,那么就可以使用动态代理机制来创建 UserService 对象了,比如:
public static void main(String[] args) {// 被代理对象UserService target = new UserService();// 通过cglib技术Enhancer enhancer = new Enhancer();// 设置要代理的类enhancer.setSuperclass(UserService.class);// 定义额外的逻辑,也就是代理逻辑enhancer.setCallbacks(new Callback[]{new MethodInterceptor() {/**** @param o 代理对象* @param method 当前正在执行的方法* @param objects 方法所需要的参数* @param methodProxy* @return* @throws Throwable*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("before...");// 执行被代理对象的原始方法Object result = methodProxy.invoke(target, objects);// Object result = methodProxy.invokeSuper(o, objects); 这种方式也可以System.out.println("after...");return result;}}});// 动态代理所创建出来的UserService代理对象UserService userService = (UserService) enhancer.create();// 执行这个userService的test方法时,就会额外执行一些其他逻辑userService.test();}
得到的都是 UserService 对象,但是执行 test()
方法时的效果却不一样了,这就是代理所带来的效果。
上面是通过 cglib
来实现的动态代理对象的创建,是基于子父类的,被代理类(UserService)是父类,代理类是子类,代理对象就是代理类的实例对象,代理类是由 cglib
创建的,对于程序员来说不用关心。
除了 cglib
技术,JDK 本身也提供了一种创建代理对象的动态代理机制,但是它只能代理接口,也就是 UserService 得先有一个接口才能利用 JDK 动态代理机制来生成一个代理对象,比如:
public interface UserInterface {public void test();}public class UserService implements UserInterface {public void test() {System.out.println("test...");}
}
利用 JDK 动态代理来生成一个代理对象:
public static void main(String[] args) {// 被代理对象UserService target = new UserService();/*** UserInterface接口的代理对象* 注意第一个参数可以是任意类的类加载器,而第二个参数必须是代理对象的类型*/Object proxy = Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserInterface.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("before...");// 通过反射机制调用被代理对象的原始方法Object result = method.invoke(target, args);System.out.println("after...");return result;}});UserInterface userService = (UserInterface) proxy;userService.test();}
如果你把 new Class[]{UserInterface.class}
,替换成 new Class[]{UserService.class}
,那么代码会直接报错:
表示一定要是个接口。
由于这个限制,所以产生的代理对象的类型是 UserInterface,而不是 UserService,这是需要注意的。
2、ProxyFactory
上面我们介绍了两种动态代理技术,那么在 Spring 中进行了封装,封装出来的类叫做 ProxyFactory,表示是创建代理对象的一个工厂,使用起来会比上面的更加方便,比如:
public static void main(String[] args) {// 被代理对象UserService target = new UserService();// 创建代理对象工厂ProxyFactory proxyFactory = new ProxyFactory();// 设置要代理的目标类proxyFactory.setTarget(target);/*** 注意:如果没有加上下面这行代码,那么默认走的是cglib动态代理;* 而如果我们设置了接口,那么走的就是jdk动态代理*/proxyFactory.setInterfaces(UserInterface);// 设置代理逻辑,MethodInterceptor表示方法拦截器proxyFactory.addAdvice(new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("before...");Object result = invocation.proceed();System.out.println("after...");return result;}});// 通过代理对象工厂,获取代理对象UserInterface userService = (UserInterface) proxyFactory.getProxy();userService.test();}
通过 ProxyFactory,我们可以不再关心到底是用 cglib
还是 JDK 动态代理了,ProxyFactory 会帮助我们去判断,如果 UserService 实现了接口,那么ProxyFactory 底层就会采用 JDK 动态代理,如果没有实现接口,就会采用 cglib
动态代理。上面的代码,就是由于 UserService 实现了 UserInterface 接口,所以最后产生的代理对象是 UserInterface 类型。
3、Advice的分类
1、Before Advice:方法调用前执行
public class XiexuBeforeAdvice implements MethodBeforeAdvice {@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println("方法调用前执行");}
}
2、After Returning Advice:方法 Return 后执行
public class XiexuAfterReturningAdvice implements AfterReturningAdvice {/*** @param returnValue 执行完被代理方法之后的返回值* @param method 被代理方法* @param args 方法所需要的参数* @param target 被代理对象* @throws Throwable*/@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {System.out.println("方法return后执行");}}
3、After Throwing Advice:方法抛异常后执行
public class XiexuThrowsAdvice implements ThrowsAdvice {/*** NullPointerException ex:当方法抛出的异常类型为NullPointerException类型时,才会调用该方法*/public void afterThrowing(Method method, Object[] args, Object target, NullPointerException ex) {System.out.println("方法抛出异常后执行");}}
4、After Advice:方法执行完之后执行,不管当前方法有没有抛出异常,这个 Advice 方法都会执行
5、Around Advice:这是功能最强大的 Advice,可以自定义执行顺序
public class XiexuAroundAdvice implements MethodInterceptor {@Nullable@Overridepublic Object invoke(@NotNull MethodInvocation invocation) throws Throwable {System.out.println("方法执行Around前");// 执行被代理方法Object proceed = invocation.proceed();System.out.println("方法执行Around后");return proceed;}
}
3.1、MethodInterceptor链
除了Around advice,其他 advice 在执行完各自的逻辑代码后,都会自动调用 proceed()
去执行被代理方法,而每次调用 proceed()
就会去看还有没有设置其他的 advice,如果有就会继续执行其他 advice 的代理逻辑。
这里就是用到了「责任链」设计模式。
public static void main(String[] args) {// 被代理对象UserService target = new UserService();// 代理对象工厂ProxyFactory proxyFactory = new ProxyFactory();// 设置目标对象proxyFactory.setTarget(target);proxyFactory.addAdvice(new XiexuBeforeAdvice());proxyFactory.addAdvice(new XiexuAroundAdvice());proxyFactory.addAdvice(new XiexuAroundAdvice());// 获取代理对象UserService proxy = (UserService) proxyFactory.getProxy();proxy.test(); // 执行这行代码的时候,底层就会去执行invocation.proceed()}
proceed()
方法源码:
@Override@Nullablepublic Object proceed() throws Throwable {/*** currentInterceptorIndex初始值为-1,每调用一个Interceptor就会加1* 当调用完了最后一个Interceptor后就会执行被代理的方法*/if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {// 执行被代理的方法,点进invokeJoinpoint()看看return invokeJoinpoint();}// currentInterceptorIndex初始值为-1Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);/*** 如果当前interceptor是InterceptorAndDynamicMethodMatcher,则先进行匹配,匹配成功后再调用该interceptor* 如果没有匹配则递归调用proceed()方法,调用下一个interceptor*/if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {InterceptorAndDynamicMethodMatcher dm =(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());// 动态匹配,根据方法参数进行匹配if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {return dm.interceptor.invoke(this);} else {// 不匹配则执行下一个MethodInterceptorreturn proceed();}} else {/*** 直接调用MethodInterceptor,传入this,在内部会再次调用proceed()方法进行递归* 比如MethodBeforeAdviceInterceptor*/return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);}}
4、Advisor的理解
跟 Advice 类似的还有一个 Advisor 的概念,一个 Advisor 是由一个 Pointcut
和一个 Advice
组成的,通过 Pointcut
可以指定需要被代理的逻辑,比如一个 UserService 类中有两个方法,按上面 ProxyFactory 的例子,这两个方法都会被代理、被增强,那么我们现在可以通过 Advisor,来控制具体代理哪一个方法,比如:
public class Test {public static void main(String[] args) {// 被代理对象UserService target = new UserService();// 代理对象工厂ProxyFactory proxyFactory = new ProxyFactory();// 设置目标对象proxyFactory.setTarget(target);proxyFactory.addAdvisor(new PointcutAdvisor() {/*** Pointcut可以去定义我们的代理逻辑要应用到哪个方法或哪个类上面* @return*/@Overridepublic Pointcut getPointcut() {return new StaticMethodMatcherPointcut() {@Overridepublic boolean matches(Method method, Class<?> targetClass) {// 表示只有test()方法才需要走代理逻辑return method.getName().equals("test");}};}/*** Advice只是表示一段代理逻辑* @return*/@Overridepublic Advice getAdvice() {return new XiexuAroundAdvice();}/*** 这个方法可以忽略* @return*/@Overridepublic boolean isPerInstance() {return false;}});// 获取代理对象UserInterface userService = (UserInterface) proxyFactory.getProxy();userService.test();}}
上面代码表示,产生的代理对象,只有在执行 test()
这个方法时才会被增强,才会执行额外的逻辑,而在执行其他方法时是不会被增强的。
5、创建代理对象的方式
上面介绍了 Spring 中所提供的 ProxyFactory、Advisor、Advice、PointCut 等技术来实现代理对象的创建,但是我们在使用 Spring 时,并不会直接这么去使用 ProxyFactory,比如说,我们希望 ProxyFactory 所产生的代理对象能直接就是 Bean,能直接从 Spring 容器中得到 UserSerivce 的代理对象,而这些 Spring 都是支持的,只不过作为开发者的我们肯定得先告诉 Spring,哪些类需要被代理,代理逻辑是什么。
5.1、ProxyFactoryBean
// 将产生的代理对象成为一个Bean@Beanpublic ProxyFactoryBean userService() {UserService userService = new UserService();// 创建ProxyFactoryBean对象ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();// 设置要代理的对象proxyFactoryBean.setTarget(userService);// 代理逻辑proxyFactoryBean.addAdvice(new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("before...");Object result = invocation.proceed();System.out.println("after...");return result;}});return proxyFactoryBean;}
通过这种方式来定义一个 UserService 的 Bean,并且是经过了 AOP 的。但是这种方式只能针对某一个 Bean。它是一个 FactoryBean,所以利用的就是FactoryBean 技术,间接地将 UserService 的代理对象作为了 Bean。
ProxyFactoryBean 还有额外的功能,比如可以把某个 Advice 或 Advisor 定义成为 Bean,然后在 ProxyFactoryBean 中进行设置:
@Beanpublic MethodInterceptor XiexuAroundAdvice() {return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("before...");Object result = invocation.proceed();System.out.println("after...");return result;}};}@Beanpublic ProxyFactoryBean userService() {// 被代理对象UserService userService = new UserService();ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();// 设置目标对象proxyFactoryBean.setTarget(userService);proxyFactoryBean.setInterceptorNames("XiexuAroundAdvice");return proxyFactoryBean;}
5.2、BeanNameAutoProxyCreator
ProxyFactoryBean 得自己指定被代理的对象,那么我们可以通过 BeanNameAutoProxyCreator 指定某个 bean 的名字,来对该 bean 进行代理
@Beanpublic MethodInterceptor XiexuAroundAdvice() {return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("before...");Object result = invocation.proceed();System.out.println("after...");return result;}};}@Beanpublic BeanNameAutoProxyCreator beanNameAutoProxyCreator() {BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();beanNameAutoProxyCreator.setBeanNames("userSe*");beanNameAutoProxyCreator.setInterceptorNames("XiexuAroundAdvice");beanNameAutoProxyCreator.setProxyTargetClass(true);return beanNameAutoProxyCreator;}
通过 BeanNameAutoProxyCreator 可以对批量的 Bean 进行 AOP,并且指定了代理逻辑,指定了一个 InterceptorName
,也就是一个 Advice,前提条件是这个 Advice 也得是一个 Bean,这样 Spring 才能找到,但是 BeanNameAutoProxyCreator 的缺点很明显,它只能根据 beanName 来指定想要代理的 Bean。
5.3、DefaultAdvisorAutoProxyCreator
public class AppConfig {/*** 定义一个Advisor类型的Bean** @return*/@Beanpublic DefaultPointcutAdvisor defaultPointcutAdvisor() {NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();pointcut.addMethodName("test");DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();defaultPointcutAdvisor.setPointcut(pointcut);defaultPointcutAdvisor.setAdvice(new XiexuAfterReturningAdvice());return defaultPointcutAdvisor;}@Beanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();return defaultAdvisorAutoProxyCreator;}}
通过 DefaultAdvisorAutoProxyCreator 会直接去找所有 Advisor 类型的 Bean,然后根据 Advisor 中的 PointCut
和 Advice
信息,确定要代理的 Bean 以及代理逻辑。
通过这种方式,我们得依靠某一个类来实现定义我们的 Advisor,或者 Advice,或者 Pointcut,那么这个步骤能不能更加简化一点呢?
答案是可以的,我们可以通过注解的方式进行简化!
比如我们可以只定义一个类,然后通过在类中的方法上加上某些注解,来定义 PointCut 以及 Advice,比如:
@Aspect@Componentpublic class XiexuAspect {@Before("execution(public void cn.xx.UserService.test())")public void xiexuBefore(JoinPoint joinPoint) {System.out.println("xiexuBefore");}}
通过上面这个类,我们就直接定义好了所要代理的方法(通过一个表达式),以及代理逻辑(被 @Before
修饰的方法),简单明了,这样对于 Spring 来说,它要做的就是来解析这些注解了,解析之后得到对应的 Pointcut
对象、Advice
对象,生成 Advisor
对象,扔进 ProxyFactory 中,进而产生对应的代理对象,具体怎么解析这些注解就是 @EnableAspectJAutoProxy
注解所要做的事情了,后面详细分析。
6、对Spring AOP的理解
OOP 表示面向对象编程,是一种编程思想,AOP 表示面向切面编程,也是一种编程思想,而我们上面所描述的就是 Spring 为了让程序员更加方便的做到面向切面编程所提供的技术支持,换句话说,就是 Spring 提供了一套机制,可以让我们更加容易的进行 AOP,所以这套机制我们也可以称之为 Spring AOP。
但是值得注意的是,上面所提供的注解的方式来定义 Pointcut
和 Advice
,Spring 并不是首创,首创是 AspectJ
,而且也不仅仅只有 Spring 提供了一套机制来支持 AOP,还有比如 JBoss 4.0、aspectwerkz 等技术都提供了对于 AOP 的支持。而刚刚说的注解的方式,Spring 是依赖了 AspectJ 的,换句话说,Spring 是直接把 AspectJ 中所定义的那些注解直接拿过来用,自己没有再重新定义了,不过也仅仅只是把注解的定义复制过来了,每个注解具体底层是怎么解析的,还是 Spring 自己做的,所以我们在使用 Spring 时,如果你想用 @Before
、@Around
等注解,是需要单独引入 AspectJ 相关 jar 包的,比如:
compile group: 'org.aspectj', name: 'aspectjrt', version: '1.9.5'
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.5'
值得注意的是:AspectJ 是在编译时对字节码进行了修改,是直接在 UserService 类对应的字节码中进行增强的,也就是可以理解为是在编译时就会去解析@Before
这些注解,然后得到代理逻辑,加入到被代理类中的字节码中去的,所以如果想用 AspectJ 技术来生成代理对象 ,是需要用单独的 AspectJ 编译器的。我们在项目中很少这么使用,我们仅仅只是用了 @Before
这些注解,而我们在启动 Spring 的过程中,Spring 会去解析这些注解,然后利用动态代理机制生成代理对象的。
IDEA 中使用 AspectJ:https://blog.csdn.net/gavin_john/article/details/80156963
7、AOP中的概念
上面我们已经提到 Advisor、Advice、PointCut 等概念了,还有一些其他的概念,首先关于 AOP 中的概念本身是比较难理解的,Spring 官网上是这么说的:
Let us begin by defining some central AOP concepts and terminology. These terms are not Spring-specific. Unfortunately, AOP terminology is not particularly intuitive. However, it would be even more confusing if Spring used its own terminology
意思是,AOP 中的这些概念并不是 Spring 特有的,而且不幸的是,AOP 中的概念不是特别直观的,如果 Spring 重新定义自己的那可能会导致更加混乱。
1、Aspect:表示切面,比如被 @Aspect
注解的类就是切面,可以在切面中去定义 Pointcut
、Advice
等等。
2、Join point:表示连接点,表示一个程序在执行过程中的一个点,比如一个方法的执行(被代理方法)、一个异常的处理,在 Spring AOP 中,一个连接点通常表示一个方法的执行。
3、Advice:表示通知,表示在一个特定连接点上所采取的动作。Advice 分为不同的类型,后面详细讨论,在很多 AOP 框架中,包括 Spring,会用Interceptor
拦截器来实现 Advice,并且会在连接点周围维护一个 Interceptor 链。
4、Pointcut:表示切点,用来匹配一个或多个连接点,Advice 与切点表达式是关联在一起的,Advice 将会执行在和切点表达式所匹配的连接点上。
5、Introduction:可以使用 @DeclareParents
来给所匹配的类添加一个接口,并指定一个默认实现。
6、Target object:目标对象,也就是被代理对象。
7、AOP Proxy:表示代理工厂,用来创建代理对象的,在 Spring Framework 中,要么是 JDK 动态代理,要么是 CGLIB 动态代理。
8、Weaving:表示织入,表示创建代理对象的动作,这个动作可以发生在编译时期(比如 Aspejctj),也可以发生在运行时期(比如 Spring AOP)。
8、Advice在Spring AOP中对应的API
上面说到的 AspjectJ 中的注解,其中有五个是用来定义 Advice 的,表示代理逻辑,以及执行时机:
1、@Before
2、@AfterReturning
3、@AfterThrowing
4、@After
5、@Around
我们前面也提到过,Spring 自己也提供了类似的实现类:
1、接口MethodBeforeAdvice,继承了接口BeforeAdvice
2、接口AfterReturningAdvice,继承了接口AfterAdvice
3、接口ThrowsAdvice,继承了接口AfterAdvice
4、接口AfterAdvice,继承了接口Advice
5、接口MethodInterceptor,继承了接口Interceptor
Spring 会把这五个注解解析成对应的 Advice 类:
1、@Before:AspectJMethodBeforeAdvice,实际上就是一个 MethodBeforeAdvice
2、@AfterReturning:AspectJAfterReturningAdvice,实际上就是一个 AfterReturningAdvice
3、@AfterThrowing:AspectJAfterThrowingAdvice,实际上就是一个 MethodInterceptor
4、@After:AspectJAfterAdvice,实际上就是一个 MethodInterceptor
5、@Around:AspectJAroundAdvice,实际上就是一个 MethodInterceptor
9、TargetSource的使用
在我们日常的 AOP 中,被代理对象就是 Bean 对象,是由 BeanFactory 给我们创建出来的,但是 Spring AOP 中提供了 TargetSource 机制,可以让我们自定义逻辑来创建被代理对象。
比如之前提到的 @Lazy
注解,当加在属性上时,会产生一个代理对象并赋值给这个属性,产生代理对象的代码为:
/*** 创建@Lazy懒加载的代理对象** @param descriptor* @param beanName* @return*/protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {// 获取Spring的Bean工厂BeanFactory beanFactory = getBeanFactory();Assert.state(beanFactory instanceof DefaultListableBeanFactory, "BeanFactory needs to be a DefaultListableBeanFactory");final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;/*** 创建TargetSource对象*/TargetSource ts = new TargetSource() {@Overridepublic Class<?> getTargetClass() {return descriptor.getDependencyType();}@Overridepublic boolean isStatic() {return false;}/*** Lazy的效果:* 当属性上有@Lazy注解,刚开始进行依赖注入时,该属性是被赋了一个代理对象,* 当你真正用到该属性时,这时候才会根据当前属性的类型和名字,去BeanFactory中找到对应的Bean,这时候才会真正去执行对应Bean的原方法。* 当set方法的参数有@Lazy注解时,同理。* @return*/@Overridepublic Object getTarget() {Set<String> autowiredBeanNames = (beanName != null ? new LinkedHashSet<>(1) : null);// 根据属性的类型和名字去Bean工厂找被代理的对象Object target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, null);if (target == null) {Class<?> type = getTargetClass();if (Map.class == type) {return Collections.emptyMap();} else if (List.class == type) {return Collections.emptyList();} else if (Set.class == type || Collection.class == type) {return Collections.emptySet();}throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(), "Optional dependency not present for lazy injection point");}if (autowiredBeanNames != null) {for (String autowiredBeanName : autowiredBeanNames) {if (dlbf.containsBean(autowiredBeanName)) {dlbf.registerDependentBean(autowiredBeanName, beanName);}}}// 找到被代理的对象,直接返回return target;}@Overridepublic void releaseTarget(Object target) {}};// 创建ProxyFactory对象ProxyFactory pf = new ProxyFactory();// 设置被代理对象为TargetSourcepf.setTargetSource(ts);Class<?> dependencyType = descriptor.getDependencyType();if (dependencyType.isInterface()) {pf.addInterface(dependencyType);}// 返回一个代理对象return pf.getProxy(dlbf.getBeanClassLoader());}
这段代码就利用了 ProxyFactory
来生成代理对象,以及使用了 TargetSource,以达到代理对象在执行某个方法时,会去调用 TargetSource 的 getTarget()
方法得到一个被代理对象。
10、ProxyFactory选择CGLIB或JDK动态代理的原理
ProxyFactory 在生成代理对象之前需要先决定到底是使用 JDK 动态代理还是 CGLIB 动态代理:
@Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {/*** config指的是我们在外面创建的ProxyFactory对象* NativeDetector.inNativeImage():当前Spring项目是不是在GraalVM虚拟机上运行的,如果是则使用JDK动态代理创建代理对象* config.isOptimize():如果isOptimize为true,则会使用cglib动态代理创建代理对象,因为Spring认为cglib比jdk动态代理要快* config.isProxyTargetClass():要代理的是不是一个类,如果为true则使用cglib动态代理创建代理对象* hasNoUserSuppliedProxyInterfaces(config):当前ProxyFactory对象有没有去添加接口(addInterface),* 如果添加了则返回false并使用JDK动态代理创建代理对象,如果没有添加接口则返回true并使用cglib动态代理创建代理对象*/if (!NativeDetector.inNativeImage() &&/*** 类似于:* ProxyFactory proxyFactory = new ProxyFactory();* proxyFactory.setOptimize(true);* proxyFactory.setProxyTargetClass(true);* 如果添加了proxyFactory.addInterface();* 那么hasNoUserSuppliedProxyInterfaces(config)为false,如果没有添加则为true*/(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {// 获取被代理类的类型Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}/*** targetClass.isInterface():如果被代理的类是一个接口* 举个例子:* ProxyFactory proxyFactory = new ProxyFactory();* proxyFactory.setTargetClass(UserInterface.class);* 这样的话就表示被代理类是一个接口* Proxy.isProxyClass(targetClass):当前所设置的被代理类是不是已经进行过JDK动态代理而生成的代理类*/if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {/*** 使用jdk动态代理创建代理对象*/return new JdkDynamicAopProxy(config);}/*** 返回Cglib创建的代理对象*/return new ObjenesisCglibAopProxy(config);} else {/*** 使用jdk动态代理创建代理对象*/return new JdkDynamicAopProxy(config);}}
11、代理对象创建过程
11.1、JdkDynamicAopProxy
1、在构造 JdkDynamicAopProxy 对象时,会先拿到被代理对象自己所实现的接口,并且额外增加 SpringProxy、Advised、DecoratingProxy 三个接口,组合成一个 Class[],并赋值给 proxiedInterfaces 属性
2、并且检查这些接口中是否定义了equals()
、hashcode()
方法
3、执行 Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this)
,得到代理对象,JdkDynamicAopProxy 作为 InvocationHandler,代理对象在执行某个方法时,会进入到 JdkDynamicAopProxy 的 invoke()
方法中
12、代理对象执行过程
1、在使用 ProxyFactory 创建代理对象之前,需要往 ProxyFactory 中先添加 Advisor
2、代理对象在执行某个方法时,会把 ProxyFactory 中的 Advisor 拿出来和当前正在执行的方法进行匹配筛选
3、把和当前正在执行的方法所匹配的 Advisor 适配成 MethodInterceptor
4、把和当前方法匹配的 MethodInterceptor 链,以及被代理对象、代理对象、代理类、当前 Method 对象、方法参数封装成 MethodInvocation 对象
5、调用 MethodInvocation 的 proceed()
方法,开始执行各个 MethodInterceptor 以及被代理对象的对应方法
6、按顺序调用每个 MethodInterceptor 的 invoke()
方法,并且会把 MethodInvocation 对象传入 invoke()
方法
7、直到执行完最后一个 MethodInterceptor 了,就会调用 invokeJoinpoint()
方法,从而执行被代理对象的当前方法
12.1、各注解对应的MethodInterceptor
1、@Before 对应的是 AspectJMethodBeforeAdvice,在进行动态代理时会把 AspectJMethodBeforeAdvice 转成 MethodBeforeAdviceInterceptor
- 先执行 advice 对应的方法
- 再执行 MethodInvocation 的
proceed()
,会执行下一个 Interceptor,如果没有下一个 Interceptor 了,会执行 target 对应的方法
2、@After 对应的是 AspectJAfterAdvice,直接实现了 MethodInterceptor
- 先执行 MethodInvocation 的
proceed()
,会执行下一个 Interceptor,如果没有下一个 Interceptor 了,会执行 target 对应的方法 - 再执行 advice 对应的方法
3、@Around 对应的是 AspectJAroundAdvice,直接实现了 MethodInterceptor
- 直接执行 advice 对应的方法,由
@Around
自己决定要不要继续往后面调用
4、@AfterThrowing 对应的是 AspectJAfterThrowingAdvice,直接实现了 MethodInterceptor
- 先执行 MethodInvocation的
proceed()
,会执行下一个 Interceptor,如果没有下一个 Interceptor 了,会执行 target 对应的方法 - 如果上面抛了 Throwable,那么则会执行 advice 对应的方法
5、@AfterReturning 对应的是 AspectJAfterReturningAdvice,在进行动态代理时会把 AspectJAfterReturningAdvice 转成 AfterReturningAdviceInterceptor
- 先执行 MethodInvocation 的
proceed()
,会执行下一个 Interceptor,如果没有下一个 Interceptor 了,会执行 target 对应的方法 - 执行上面的方法后得到最终的方法的返回值
- 再执行 Advice 对应的方法
13、AbstractAdvisorAutoProxyCreator
DefaultAdvisorAutoProxyCreator 的父类是 AbstractAdvisorAutoProxyCreator。
AbstractAdvisorAutoProxyCreator 非常强大以及重要,只要 Spring 容器中存在这个类型的 Bean,就相当于开启了 AOP,AbstractAdvisorAutoProxyCreator实际上就是一个 BeanPostProcessor
,所以在创建某个 Bean 时,就会进入到它对应的生命周期方法中,比如在某个 Bean 初始化之后,会调用wrapIfNecessary()
方法进行 AOP,底层逻辑是:AbstractAdvisorAutoProxyCreator 会找到所有的 Advisor,然后判断当前这个 Bean 是否存在某个 Advisor 与之匹配(根据 Pointcut),如果匹配就表示当前这个 Bean 有对应的切面逻辑,需要进行AOP,需要产生一个代理对象。
14、@EnableAspectJAutoProxy
这个注解主要就是往 Spring 容器中添加了一个 AnnotationAwareAspectJAutoProxyCreator
类型的Bean。
AspectJAwareAdvisorAutoProxyCreator
继承了 AbstractAdvisorAutoProxyCreator
,重写了 findCandidateAdvisors()
方法,AbstractAdvisorAutoProxyCreator
只能找到所有 Advisor 类型的 Bean 对象,但是 AspectJAwareAdvisorAutoProxyCreator 除了可以找到所有 Advisor 类型的 Bean 对象,还能把 @Aspect
注解所标注的 Bean 中的 @Before
等注解及方法进行解析,并生成对应的 Advisor 对象。
@Aspect
@Component
public class XxAspect {@Before("execution(public void cn.xx.UserService.test())")public void xiexuBefore(JoinPoint joinPoint) {System.out.println("xiexuBefore");}}
所以,我们可以这样理解 @EnableAspectJAutoProxy
,其实就是向 Spring 容器中添加了一个 AbstractAdvisorAutoProxyCreator
类型的Bean,从而开启了 AOP,并且还会解析 @Before
等注解并生成 Advisor
。
15、Spring中AOP原理流程图
https://www.processon.com/view/link/5faa4ccce0b34d7a1aa2a9a5
相关文章:
Spring之AOP底层源码解析
Spring之AOP底层源码解析 1、动态代理 代理模式的解释:为其他对象提供一种代理以控制对这个对象的访问,增强一个类中的某个方法,对程序进行扩展。 举个例子 public class UserService {public void test() {System.out.println("test.…...
人脸识别——景联文科技提供3D头模数据采集业务!
“拿起手机刷脸解锁、上下班考勤、支付订单,刷脸已极大地便利了我们的生活。清华大学新闻学院教授沈阳表示,中国人平均每天要暴露在各种摄像头下超过500次。人脸识别已成了我们生活中重要的一部分。由于2D人脸识别容易受到姿态、表情、光照等因素影响&am…...
SpringBoot集成Flink-CDC 采集PostgreSQL变更数据发布到Kafka
最近做的一个项目,使用的是pg数据库,公司没有成熟的DCD组件,为了实现数据变更消息发布的功能,我使用SpringBoot集成Flink-CDC 采集PostgreSQL变更数据发布到Kafka。 一、业务价值 监听数据变化,进行异步通知…...
酷开系统壁纸模式,将氛围感死死拿捏!
古希腊哲学家柏拉图曾经说过:“美感是起于视觉、听觉产生的快感,以人的感官所能达到的范围为极限。”而电视则恰恰就是视觉听觉的完美融合体,当一台开启的电视可以给我们带来视听享受的时候,一台待机状态下的电视又如何取悦于我们…...
第0章 一些你可能正感到迷惑的问题
操作系统是什么 操作系统是控制管理计算机系统的硬软件,分配调度资源的系统软件。 由操作系统把资源获取到后台给用户进程,但为了保护计算机系统不被损坏,不允许用户进程直接访问硬件资源。 操作系统相当于是一个分配资源的机构,…...
MYSQL实战
SQL的处理 缓存解析查询优化(查询优化器) 重写查询;表的读取顺序;选择索引1.不要在索引上做任何操作 表达式函数 2.尽量全值匹配 联合索引中搜素条件后会根据最优条件排序进行查询,联合索引尽量都使用起来。搜索条…...
少儿户外拓展北斗定位解决方案
一、项目背景户外拓展训练是指通过专业的机构,对久居城市的人进行的一种野外生存训练。拓展训练通常利用崇山峻岭、翰海大川等自然环境,通过精心设计的活动达到“磨练意志、陶冶情操、完善人格、熔炼团队”的培训目的。针对户外拓展人员安全管理存在的实…...
更换ssl证书
更换ssl证书常用证书查看以及转换网址阿里云判断流量以及配置证书判断接入点阿里云控制台配置证书WAFAzure判断流量以及配置证书:判断接入点Azure配置证书CDNAPP GateWay常用证书查看以及转换网址 https://www.chinassl.net/ssltools/convert-ssl.htmlhttps://myss…...
线程池源码解析项目中如何配置线程池
目录 基础回顾 线程池执行任务流程 简单使用 构造函数 execute方法 execute中出现的ctl属性 execute中出现的addWorker方法 addWorker中出现的addWorkerFailed方法 addWorker中出现的Worker类 Worker类中run方法出现的runWorker方法 runWorker中出现的getTask runWo…...
Echarts 更改K线度颜色,解释K线图4个数字意义
第019个点击查看专栏目录本示例修改K线度的颜色,方法参考源代码。 这里面讲一下K线图的四个数字,如[20, 34, 10, 38], 第一位:20代表开盘价格, 第二位:34代表闭盘价格, 第三位:10代表最低价&…...
JavaScript和Java两种方法实现百度地图和高德、腾讯地图的相互转换
目录一、常见的经纬度标准二、百度地图和高德、腾讯地图经纬度的转换1、前端JavaScript转换2、后端Java实现转换一、常见的经纬度标准 高德、腾讯(使用GCJ02) GCJ-02坐标系,也称火星坐标系,由中国国家测绘局在02年发布࿰…...
Vue中常见的几种组件间通信方法
1.props(父传子) 父组件Parent.vue <template><child :msg"message"></child> </template>父组件通过:val"value"的形式定义要传给子组件的值value绑定到val上 子组件Child.vue export default {//写法一…...
Outcome VS. Output:研发效能提升中,谁会更胜一筹?
2007 年,网景通信公司(Netscape)的联合创始人 Marc Andreessen 在博客 The Pmarca Guide to Startups 中提出 「Product/Market Fit」 ,他写道, 「这意味着在一个良好的市场中,拥有能够满足该市场的产品。」…...
ptp4l与phc2sys进行系统时钟同步
linuxptp用于时钟同步。安装采用apt install linuxptp主要包含2个程序,ptp4l 进行时钟同步,实时网卡时钟与远端的时钟同步,支持1588 和 802.1AS 两种协议phc2sys 将网卡上的时钟同步到操作系统,或者反之命令demo:某主机P通过eth2连…...
使用注解JSON序列化
JsonSerialize(using ToStringSerializer.class) 将返回数据转成String序列化 JsonFormat(pattern "yyyy-MM-dd hh:mm",timezone"GMT8") 将日期数据转换成特定格式 使用JsonSerialize自定义注解接口 定义接口 import java.lang.annotation.ElementTyp…...
kubernetes教程 --Pod生命周期
Pod生命周期 pod创建过程运行初始化容器(init container)过程运行主容器(main container)过程 容器启动后钩子(post start)、容器终止前钩子(pre stop)容器的存活性探测(…...
高校房产管理系统用到了哪些技术?
数图互通高校房产管理系统是基于公司自主研发的FMCenterV5.0平通过在中国100多所高校的成功实施和迭代,形成了一套成熟、完善、全生命周期的房屋资源管理解决方案。台,是针对中国高校房产的管理特点和管理要求,研发的一套标准产品;…...
【Python学习笔记】37.Python3 MySQL - mysql-connector 驱动(2)
前言 本章继续介绍MySQL - mysql-connector 驱动。 where 条件语句 如果我们要读取指定条件的数据,可以使用 where 语句: demo_mysql_test.py 读取 name 字段为 CSDN 的记录: import mysql.connectormydb mysql.connector.connect(host…...
【高级Java】高级Java实验
一、反射与动态代理1、(4分)请通过反射技术,为附件中的Person.class生成相应的.java代码,java代码中的方法的方法体为空,即方法内部代码不用生成。请注意生成的java代码的格式。2、(3分)请为第1…...
SYN480R 解码
目录1.空载情况下2.当有按键被按下3.数据帧分析4.同步码5.数据码6.对24位数据帧分析1.空载情况下 在空载情况下,syn480r 输出引脚,输出的是杂乱无序的波形 2.当有按键被按下 按下按键,会连续输出相同的脉冲波形,放大分析 3.数据…...
ASP .NET(基于.NET 6.0)源码解读
这几天一直在琢磨在我现有技术认知基础上,未来如何做技术提升。 日思夜想,我整理出了我自己的一套学习规划方案,并希望在实施过程中能够不断调整学习方案与方式,以接近自我提升的效率最大化。 从以下几个大的方面来得到提升&…...
阿里工作7年,一个30岁女软件测试工程师的心路历程
简单的先说一下,坐标杭州,14届本科毕业,算上年前在阿里巴巴的面试,一共有面试了有6家公司(因为不想请假,因此只是每个晚上去其他公司面试,所以面试的公司比较少) 其中成功的有4家&am…...
学生党必备的 Keychron 无线机械键盘
学生党必备的 Keychron 无线机械键盘 由于专业需要,之间的键盘使用起来不太舒服,于是准备重新买一个适合工作学习的键盘,于是通过朋友介绍了解到了keychron k3pro,当时也看到网上一些资料说道这款键盘比较到位,今天就来带大家了解…...
FPGA MAX 10 10M50系列10M50DAF484C8G/10M50DAF484C7G/10M50DCF484C7G规格
介绍MAX 10器件是单芯片、非易失性低成本可编程逻辑器件(pld),用于集成最优的系统组件集。MAX 10设备的亮点包括:内部存储双配置闪存用户闪存即时支持集成模数转换器(adc)支持Nios II单芯片软核处理器MAX 10设备是系统管理、I/O扩展、通信控制平面、工业、汽车和消费…...
【codequ】Java学习路线整理(韩顺平)
文章目录Java学习路线一、Java基础1.建立编程思想Java概述变量运算符控制结构数据、排序和查找面向对象编程(基础)面向对象编程(中级)项目&学以致用2.提升编程能力3.分析需求,代码实现能力Java8新特性二、Java高级…...
服务器容器配置日志(Linux+x86_64+Ubuntu18.04+CUDA11.0+python3.7)
一、创建并进入容器 (平台使用教学详细,这部分略写) 登上服务器后,打开终端输入如下进入自己建的容器 ssh -p XXXXX root10.XXX.XXX.XXX //按自己的宿主机端口写二、安装Conda(miniconda3) (…...
2023年美赛赛题思路分析
2023年的赛题A-F题的整体难度不算太难,难度在于数据的收集上。整体难度上来看,难度上F题难度最小,建议直接上手。本次先给大家分享一些数据网站,在对各题做简单的思路分析。1、美国国家海洋和大气管理局Homepage | National Ocean…...
[C++]服务器与客户端建立连接与检测断开的demo
该程序在IP127.0.0.1以及端口5000环境下测试 有一段时间没有在Windows下用C进行网络编程了,这段日子都在做QT的网络编程和OpenCV的图像识别。 今天重新写个Windows下C的,基于TCP的双端连接建立与断开检测的demo,巩固下自己Windows下的网络编程…...
包教包会vue3+ts状态管理工具pinia
一、Pinia介绍 定义:pinia是和vuex一样的状态管理工具 语法:和 Vue3 一样,它实现状态管理有两种语法:选项式API 和 组合式API 支持:vue2、typeScript、devtools 二、使用步骤 1.安装 pnpm add pinia yarn add pin…...
Generated columns cannot be used in COPY
错误如下DBD::Pg::db do failed: ERROR: column "transtype" is a generated columnsec., avg: 2520 recs/sec), REPORTSINTERMEDIATETABLE in progress.DETAIL: Generated columns cannot be used in COPY. at /usr/local/share/perl5/Ora2Pg.pm line 15125.FATAL: …...
网站Api接口怎么做/优化服务内容
前言:这是一个基本涵盖Java初中级大部分核心知识点的面试题集,包含了Java基础、容器、多线程、Spring、SpringBoot、MyBatis、Linux、MySQL、Redis、MongoDB、网络协议、JVM等方向。所有题目都是我亲自整理的。 因为无法生成自动跳转的目录,…...
虚拟机怎么做多个网站/网站免费搭建平台
在拍摄人像照片时,由于受环境色的影响,人的皮肤上通常有部分区域产生偏色(Color Cast)现象。比如,在绿草地或绿色植物附近拍摄时,部分皮肤受反射的绿光的影响而染上难看的绿色。对于黄种人来说,…...
做游戏直播什么游戏视频网站好/免费的网站域名查询
一、使用top命令查看占用高资源的java项目的进程ID(pid): top 二、查看该进程中的线程所占用资源的情况:top -Hp pid 三、查看该线程对应的16进制:printf %x 11129 打印并保存该进程中堆栈的使用信息日志:jstack -l 11095 >> jstack.lo…...
成都装修公司推荐/广东seo推广
struts2 之前没用过,也没打算学习,应为听大家说效率好像有点慢而且使用起来比较麻烦,但是换了工作后发现公司使用的是struts2所以只能用了,因为之前没学过(一点不了解)所以怎么访问的都会不,怎么…...
静态网站提交表单怎么做/环球军事网最新军事新闻最新消息
默认使用sqllite数据库 修改为mysql数据库 创建数据库 在app models中编写创建数据库类 from django.db import models class Book(models.Model):#表明book django 会自动使用项目名我们自定义的表名# 如果没有自定义主键,django会自动添加一个主键,字段…...
asp网站的安全性/深圳竞价托管公司
语法格式: CONVERT(data_type,expression[,style]) 说明: 此样式一般在时间类型(datetime,smalldatetime)与字符串类型(nchar,nvarchar,char,varchar) 相互转换的时候才用到. 例子: SELECT CONVERT(varchar(30),getdate(),101) now 结果为 now -------------------------------…...