Spring往期精彩文章

前言

我们都知道Java是一门面向对象(OOP)的语言,所谓万物皆对象。但是它也存在着一些个弊端:当你需要给多个不具有继承关系的对象引入同一个公共的行为的时候,例如日志,安全检测等等,我们只能在每个对象中去引入这个公共行为,这样就产生了大量的重复代码,并且耦合度也会很高,不利于维护。正因如此就产生了面向切面(AOP)编程。可以说有了AOP使得面向对象更加完善,是对其的一个补充,AOP所关注的方式是横向的,不同于OOP的纵向,接下来我们详细讲解一下spring中的AOP。

AOP的使用

我们先从动态AOP开始

  • 首先引入Aspect
  1. <dependency>
  2. <groupId>org.aspectj</groupId>
  3. <artifactId>aspectjweaver</artifactId>
  4. <version>1.9.7</version>
  5. </dependency>
  • 创建用于拦截的测试Bean
  1. package com.vipbbo.selfdemo.spring.aop.test;
  2. public class TestBean {
  3. private String message = "Test Message";
  4. public String getMessage() {
  5. return message;
  6. }
  7. public void setMessage(String message) {
  8. this.message = message;
  9. }
  10. public void test(){
  11. System.out.println(this.message);
  12. }
  13. }
  • 创建Advisor

spring中一改以往摒弃了它最原始的繁杂的配置方式,目前采用@AspectJ注解的方式对POJO进行标注,使得AOP的工作大大简化。例如在AspectJTest类中,我们要做的就是在所有类的test方法执行前在控制台输出beforeTest,在所有类的test方法执行后打印afterTest,同时又使用环绕通知的方式在所有类的方法执行前后在此分别打印around......beforearound......after

AspectJTest代码

  1. @Aspect
  2. public class AspectJTest {
  3. @Pointcut("execution(* *.test(..))")
  4. public void test(){
  5. }
  6. @Before("test()")
  7. public void beforeTest(){
  8. System.out.println("beforeTest");
  9. }
  10. @Around("test()")
  11. public Object aroundTest(ProceedingJoinPoint joinPoint){
  12. System.out.println("around.........before");
  13. Object proceed = null;
  14. try {
  15. proceed = joinPoint.proceed();
  16. } catch (Throwable throwable) {
  17. throwable.printStackTrace();
  18. }
  19. System.out.println("around.........after");
  20. return proceed;
  21. }
  22. @After("test()")
  23. public void afterTest(){
  24. System.out.println("afterTest");
  25. }
  26. }
  • 创建配置文件
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  6. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
  7. >
  8. <!--开启AOP 自动配置-->
  9. <aop:aspectj-autoproxy/>
  10. <bean id="testBean" class="com.vipbbo.selfdemo.spring.aop.test.TestBean">
  11. <property name="message" value="一个苦逼的程序员"/>
  12. </bean>
  13. <bean id="aspect" class="com.vipbbo.selfdemo.spring.aop.test.AspectJTest"/>
  14. </beans>

在编写配置文件中要注意图中的声明、命名空间:

  • 测试类
  1. public class Test {
  2. public static void main(String[] args) {
  3. ApplicationContext ac = new ClassPathXmlApplicationContext("spring-aop.xml");
  4. TestBean testBean = (TestBean) ac.getBean("testBean");
  5. testBean.test();
  6. }
  7. }

运行结果如下:

通过上述代码可以看出,Spring实现了对所有类的test方法进行了增强,使得辅助功能(日志等)可以独立出来,也做到了解耦和对程序的扩展。那么Spring是如何实现AOP的呢?实现我们知道,Spring是由一个配置文件控制是否支持注解的AOP,也就是<aop:aspectj-autoproxy/>,当配置文件有了这句声明的时候,Spring就会支持注解的AOP,那么分析从这里开始。

AOP自定义注解源码解读

我们知道Spring中的自定义注解,如果声明了自定义注解,那么在Spring中的一个地方一定注册了对应的解析器,我们从aspectj-autoProxy入手:

  1. Spring源码中全局搜索,我们发现了在包`org.springframework.aop.config`下的`AopNamespaceHandler`,然后我们打开这个类

AopNamespaceHandler类中我们发现了这个init函数

  1. public class AopNamespaceHandler extends NamespaceHandlerSupport {
  2. /**
  3. * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
  4. * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
  5. * and '{@code scoped-proxy}' tags.
  6. */
  7. @Override
  8. public void init() {
  9. // In 2.0 XSD as well as in 2.5+ XSDs
  10. registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
  11. registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
  12. registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
  13. // Only in 2.0 XSD: moved to context namespace in 2.5+
  14. registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
  15. }
  16. }

从上述代码可以看出,在解析配置文件的时候,一旦遇到aspectj-autoproxy就会使用AspectJAutoProxyBeanDefinitionParser解析器进行解析,接下来我们该函数的具体实现:

注册AnnotationAwareAspectJAutoProxyCreator

所有的解析器都是对接口BeanDefinitionParser的实现,入口都是从parse函数开始的,AnnotationAwareAspectJAutoProxyCreatorparse函数如下:

  • 看源码(具体实现在AspectJAutoProxyBeanDefinitionParser.class)
  1. @Override
  2. @Nullable
  3. public BeanDefinition parse(Element element, ParserContext parserContext) {
  4. // 注册AnnotationAwareAspectJAutoProxyCreator
  5. AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
  6. // 对于注解中的子类进行处理
  7. extendBeanDefinition(element, parserContext);
  8. return null;
  9. }

从上述代码我们又看出具体实现逻辑是在registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中实现的,继续进入到函数方法体内:

  • 看源码(具体实现在AopNamespaceUtils.class)
  1. /**
  2. * 注册AnnotationAwareAspectJAutoProxyCreator
  3. * @param parserContext
  4. * @param sourceElement
  5. */
  6. public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
  7. ParserContext parserContext, Element sourceElement) {
  8. // 注册或升级 AutoProxyCreator定义beanName为org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition
  9. BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
  10. parserContext.getRegistry(), parserContext.extractSource(sourceElement));
  11. // 对于proxy-target-class以及expose-proxy属性的处理
  12. useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
  13. // 注册组件并通知,便于监听器作进一步处理
  14. registerComponentIfNecessary(beanDefinition, parserContext);
  15. }

看上述源码可知在函数registerAspectJAnnotationAutoProxyCreatorIfNecessary中主要做了三件事,基本是每行代码做了一件。接下来我们一一解析:

函数体内的registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法

注册或升级AnnotationAwareAspectJAutoProxyCreator

对于AOP的实现基本都是靠AnnotationAwareAspectJAutoProxyCreator来完成的,它可以根据@Pointcut注解定义的节点来自动代理相匹配的bean,但是为了配置简单,Spring使用了自动配置来帮我们自动注册AnnotationAwareAspectJAutoProxyCreator,其过程就是在这里实现的。我们继续跟进方法内部:

  • 看源码(具体实现在AopConfigUtils.class)
  1. @Nullable
  2. public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
  3. BeanDefinitionRegistry registry, @Nullable Object source) {
  4. return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
  5. }

在上面代码中我们看到了函数registerOrEscalateApcAsRequired继续跟进:

  • 看源码(具体实现在AopConfigUtils.class)
  1. @Nullable
  2. private static BeanDefinition registerOrEscalateApcAsRequired(
  3. Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
  4. Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
  5. // 如果已经存在了自动代理创建器 且存在的自动代理创建器与现在的不一致,那么需要根据优先级判断到底需要使用哪一个
  6. if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
  7. BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
  8. if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
  9. int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
  10. int requiredPriority = findPriorityForClass(cls);
  11. if (currentPriority < requiredPriority) {
  12. // 改变bean最重要的就是改变bean所对应的className属性
  13. apcDefinition.setBeanClassName(cls.getName());
  14. }
  15. }
  16. return null;
  17. }
  18. // 注册BeanDefinition,Class为AnnotationAwareAspectJAutoProxyCreator.class,beanName为internalAutoProxyCreator
  19. RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
  20. beanDefinition.setSource(source);
  21. beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
  22. beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  23. registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
  24. return beanDefinition;
  25. }

同时我们也要看一下AopConfigUtils类中的这部分代码:

  1. /**
  2. * The bean name of the internally managed auto-proxy creator.
  3. */
  4. public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
  5. "org.springframework.aop.config.internalAutoProxyCreator";

以上代码实现了自动注册AnnotationAwareAspectJAutoProxyCreator类的功能,同时这里还设计到一个优先级的问题,假设如果已经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底使用哪一个

处理proxy-target-class以及expose-proxy属性

useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);这部分做了对proxy-target-class以及expose-proxy属性的处理。

  • 看源码(具体实现在AopNamespaceUtils。class)
  1. private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
  2. if (sourceElement != null) {
  3. // 实现了对 proxy-target-class的处理
  4. boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
  5. if (proxyTargetClass) {
  6. AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
  7. }
  8. // 对expose-proxy的处理
  9. boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
  10. if (exposeProxy) {
  11. AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
  12. }
  13. }
  14. }
  1. 在上述代码中使用到了**两个强制使用的方法**分别是`forceAutoProxyCreatorToUseClassProxying``forceAutoProxyCreatorToExposeProxy`,强制使用的过程其实也是一个属性设置的过程,两个函数的具体实现如下(具体实现在`AopConfigUtils.class`):
  1. public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
  2. if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
  3. BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
  4. definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
  5. }
  6. }
  7. public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
  8. if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
  9. BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
  10. definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
  11. }
  12. }

接下来让我们说一下proxy-target-classexpose-proxy这两个属性

  • proxy-target-proxy :Spring AOP部分使用的JDK动态代理或者是CGLIB代理来为目标对象创建代理。(这里建议尽量使用JDK动态代理),如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有目标类型实现的接口都将被代理;倘若目标对象没有实现任何接口,则会创建一个CGLIB代理。

    另外如果你想强制使用CGLIB代理的话,(例如希望代理目标对象的所有方法,而不只是实现子接口的方法)那也是可以的,但是需要考虑两个问题

    1. 无法通知(advise)Final方法,因为它们不能被重写
    2. 你需要将CGLIB二进制发行包放在classpath下面

    与之相比较,JDK本身就提供了动态代理,强制使用CGLIB代理需要将<aop-config>中的proxy-target-class属性设置为true。

    1. <aop:config proxy-target-class="true"/>

当你使用CGLIB代理@AspectJ自动代理支持,可以按照以下方式设置

  1. <aop:aspectj-autoproxy proxy-target-class="true"/>
  • expose-proxy有时候目标对象内部的自我调用将无法实施切面中的增强,如下:
  1. public interface AService {
  2. public void a();
  3. public void b();
  4. }
  5. @Service()
  6. public class AServicelmpll implements AService {
  7. @Transactional(propagation = Propagation.REQUIRED)
  8. public void a() {
  9. this.b{);
  10. }
  11. @Transactional(propagation = Propagation.REQUIRES_NEW)
  12. public void b() {
  13. }
  14. }
  1. 此处的this指向目标对象,因此调用this.b将不会执行b的事务切面,即不会执行事务增强,因此b方法的事务定义` @Transactional(propagation = Propagation.REQUIRES_NEW) `将不会实施,为了解决这个问题,我们可以这样做:
  1. <aop:aspectj-autoproxy expose-proxy = "true"/>
  1. 然后将以上代码中的`this.b()`修改为`((AService)AopContext.currentProxy()).b()`即可

通过以上的修改便可完成对ab方法的同时增强

简单说一下JDK动态代理的CGLIB代理

  • JDK动态代理:其对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的创建。
  • CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的Java字节码编辑类库)操作字节码类实现的,性能要比JDK强

微信搜索【码上遇见你】获取更多精彩文章,以及学习资料

Spring源码之AOP的使用的更多相关文章

  1. Spring 源码学习——Aop

    Spring 源码学习--Aop 什么是 AOP 以下是百度百科的解释:AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程通过预编译的方式和运行期动态代理实 ...

  2. 专治不会看源码的毛病--spring源码解析AOP篇

    昨天有个大牛说我啰嗦,眼光比较细碎,看不到重点.太他爷爷的有道理了!要说看人品,还是女孩子强一些.原来记得看到一个男孩子的抱怨,说怎么两人刚刚开始在一起,女孩子在心里就已经和他过完了一辈子.哥哥们,不 ...

  3. Spring源码解析-AOP简单分析

    AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等等,不需要去修改业务相关的代码. 对于这部分内容,同样采用一个简单的例子和源码来说明. 接口 public ...

  4. 面试真题--------spring源码解析AOP

    接着上一章对IOC的理解之后,再看看AOP的底层是如何工作的. 1.实现AOP的过程    首先我们要明白,Spring中实现AOP,就是生成一个代理,然后在使用的时候调用代理. 1.1 创建代理工厂 ...

  5. spring源码解读-aop

    aop是指面向切面编程,ProxyFactoryBean是spring aop的底层实现与源头,为什么这么说呢?首先我们看一段配置: 1.target是目标对象,需要对其进行切面增强 2.proxyI ...

  6. Spring源码学习

    Spring源码学习--ClassPathXmlApplicationContext(一) spring源码学习--FileSystemXmlApplicationContext(二) spring源 ...

  7. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

  8. spring源码学习之路---深入AOP(终)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...

  9. spring源码学习之路---AOP初探(六)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,但当初打算学习 ...

随机推荐

  1. 【java虚拟机】分代垃圾回收策略的基础概念

    作者:平凡希 原文地址:https://www.cnblogs.com/xiaoxi/p/6602166.html 一.为什么要分代 分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一 ...

  2. 解决maven中静态资源只能放到properties中的问题

    构建Maven项目的时候,如果没有进行特殊的配置,Maven会按照标准的目录结构查找和处理各种类型文件. Maven项目的标准目录结构 src main java         源文件 resour ...

  3. Java并发之AQS原理解读(二)

    上一篇: Java并发之AQS原理解读(一) 前言 本文从源码角度分析AQS独占锁工作原理,并介绍ReentranLock如何应用. 独占锁工作原理 独占锁即每次只有一个线程可以获得同一个锁资源. 获 ...

  4. 重启网络服务 network 出现问题

    2021-08-24 地址冲突了,因为想要设置成静态 ip 一直都不对,情急之下就将本地 ip 设置成了虚拟机的 ip,故出现此错误 后将地址改掉,重启网络服务就没有错误了 一开始我设置的虚拟网卡 n ...

  5. vue 之 v-model

    v-model虽然很像使用了双向数据绑定的 Angular 的 ng-model,但是 Vue 是单项数据流,v-model 只是语法糖而已: <input v-model="sth& ...

  6. Qt5之正则表达式

    字符 描述 \ 将下一个字符标记为一个特殊字符.或一个原义字符.或一个 向后引用.或一个八进制转义符.例如,'n' 匹配字符 "n".'\n' 匹配一个换行符.序列 '\\' 匹配 ...

  7. ElasticSearch集群的安装(windows)

    首先尽量保持你的磁盘空间足够大,比如你下载的软件的放在D盘,D盘尽量保持10G以上,还有C盘也差不多10G以上比较保险 一.下载 1)目前我下载的版本是elasticsearch-7.12.0-win ...

  8. PPP协议、PPPoE协议、L2TP协议的关系

    1. 简述 首先对这3中协议做一个简单的描述: 协议 协议类型 描述 PPP 点对点链路层协议 应用最广泛的点对点协议,可应用在多种网络,改善了SLIP协议的不足 PPPoE 点对点链路层协议 对PP ...

  9. 地址栏url中去掉所有参数

    1.地址栏url中去掉所有参数,这个是纯前端解决,很多时候页面跳转时候会选择在url后面带参数过去,(使用?&),方便传也方便取,但是我们要做的是不要让页面的一些请求参数暴露在外面 正常项目工 ...

  10. RabbitMQ核心知识总结!

    本文已经收录到github仓库,此仓库用于分享Java相关知识总结,包括Java基础.MySQL.Spring Boot.MyBatis.Redis.RabbitMQ.计算机网络.数据结构与算法等等, ...