Spring|面向切面编程(AOP)


认识Spring AOP(Aspect Oriented Program)

AOP简介

AOP(面向切面编程)是Spring的一个关键组件,AOP补充了Spring IOC ,为其提供了中间件解决方案,还在数据库事务中被广泛使用。

AOP目的

将核心业务(核心关注点)和周边功能开发(称为横切关注点,例如日志记录、权限认证、事务处理)进行隔离,从而耦合度降低,提高了软件的可重用性和开发效率。
其实AOP的作用就是在不改变代码的情况下,对功能进行了增强,符合开闭原则。

AOP核心概念

  • 横切关注点
    就是对哪个方法进行拦截,拦截后怎么处理,这些关注点就是横切关注点。
  • 连接点(joinpoint)
    被拦截到的方法,实际上连接点还可以是字段或者构造器。
  • 切入点(Pointcut)
    对连接点的拦截进行定义,在哪些方法上进行切入。
  • 通知/增强(advice)
    就是对拦截到的之后要执行的代码,比如说日志、事务等。通知分为前置、后置、异常、最终、环绕通知五类。
  • 切面(aspect)
    切入点和通知的组合,就是在什么时候,什么地方,做什么增强。
  • 织入(weave)
    将切面应用到目标对象中,并创建代理的过程。
  • 目标对象
    代理的目标对象,就是表现成、业务层、持久层相关代码。
  • 代理(Proxy)
    最后生成的对象

注解式通知类型

注解 说明 应用场景
@Before 前置通知,在连接点之前执行通知,不能阻止连接点之前执行的操作。 方法开始时可以进行效验
@After 后置通知,在连接点之后执行通知。 修改方法的返回值
@Around 环绕通知,环绕通知可以在方法调用前后完成自定义的行为 事务
@AfterReturning 返回通知,在连接点执行正常返回后调用 资源释放
@AfterThrowing 异常通知,在某方法抛出异常退出时执行通知 包装异常

使用注解实现Spring AOP

  1. 导入依赖的jar包
    构建的maven项目,就在pom.xml里面添加
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
  1. 创建一个实现主要业务的类DeptService
@Service  //自动注册到Spring容器,不要配置文件定义bean了
public class DeptService {

    public void deptAdd(){
        System.out.println("添加部门(主要业务)");
    }
}
  1. 创建一个次要业务类LogAspect
@Aspect //只有添加了@Aspect注解的Java类才会被Spring容器解释为InvocationHandler
@Component  //把普通的pojo实例化到Spring容器中,相当于配置文件中的<bean id="" class=""/>
public class LogAspect{
    //切入点,"execution(里面配置的是方法的路径)"
    @Pointcut("execution(public void com.beans.DeptService.deptAdd())")
    public void pointCut(){
        
    }
   //前置通知,在连接点方法前调用
    @Before("pointCut()")
    public void startMethod(){
        System.out.println("deptAdd方法将要执行......");
    }
   //后置通知,在连接点方法后调用 
    @After("  ()")
    public void endMethod(){
        System.out.println("deptAdd方法执行完毕......");
    }
}
  1. 写个配置类ApplicationConfig 替换掉XML
//一定要在配置类添加【EnableAspectJAutoProxy】
@EnableAspectJAutoProxy //开启AOP
@ComponentScan(value={"com.beans"}) //定义扫描路径从中找出标识了需要配置的类自动装配到spring的bean容器中
@Configuration//相当于配置文件xml
public class ApplicationConfig {
  
}
  1. 开始测试
public class TestMain {

    public static void main(String[] args) {
       // 1.获得Spring容器对象

       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
       DeptService service = (DeptService) context.getBean("deptService");
       service.deptAdd();S
       context.close();
    }
}

运行测试的代码结果如下:

deptAdd方法将要执行......
添加部门(主要业务)
deptAdd方法执行完毕......

使用XML配置文件

<bean class="目标类的全路径"></bean>
<bean class="通知类的全路径"></bean>
<aop:config>
    <!-- 这是使用的Spring AOP实现 -->
    <!-- <aop:advisor advice-ref="" pointcut=""/> -->   
    
    <!-- 配置切面:切面由通知和切入点组成 -->
    <aop:aspect ref="通知类 ">
        <!-- method属性值:指的是通知类的方法名称 -->
        <!-- pointcut属性值:切入点表达式 -->
        <aop:before method="before"
                pointcut="execution(* 包名.类名.方法名(参数类型))" />
    </aop:aspect>
</aop:config>

Spring配置文件,替换上面的@Configuration

    <!-- 扫描切面类和目标类 -->
    <context:component-scan
        base-package="com.spring.beans"></context:component-scan>
    
    <!-- 开启aspectj的自动代理,用于AOP的注解方式 -->
    <aop:aspectj-autoproxy/>

主要业务(DeptService )和次要业务(LogAspect)是怎么关联到一起的

自定义代理模式

  Proxy.newInstance(DeptService.class.getClassLoader(),
                                      DeptService.class.getIntgerfaces(),logAspect)

Spring中开启AOP(@EnableAspectJAutoProxy)

  1. 查看@EnableAspectJAutoProxy源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
    //AOP具体实现方式,是否创建基于CGLIB代理,而不是java的JDK代理
    boolean proxyTargetClass() default false;
    //控制代理的暴露方式,解决内部调用不能使用代理的场景,默认为false.
    boolean exposeProxy() default false;

}
  1. 核心是@Import(AspectJAutoProxyRegistrar.class)
    @Import():用指定类进行加载,把某一个类(AspectJAutoProxyRegistrar.class)加载到Spring容器中
  2. 查看这个类AspectJAutoProxyRegistrar.class
    发现了AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar 接口,进去看这个接口类,看翻译介绍,原来是个Bean定义注册器,它有权利直接将某个java类注册到Spring容器中。
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(
        AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }

}

重点就在AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);这里可以跟着debug走一遍。

  1. AnnotationAwareAspectJAutoProxyCreator.class
    AnnotationAwareAspectJAutoProxyCreator.class把这个类定义为BeanDefinition放到spring容器中
@Nullable
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
        return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
    }

    @Nullable
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
            @Nullable Object source) {

        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

看看下面这个类关系图:


AspectJAwareAdvisorAutoProxyCreator
AbstractAdvisorAutoProxyCreator
AbstractAutoProxyCreator
实现SmartInstantiationAwareBeanPostProcessor接口-->BeanPostProcessor
实现BeanFactoryAware接口 -->Aware
这里可以看下:Spring中的Aware接口
Aware
被【bean后置处理器】拦截的bean对象,只有实现了指定的Aware接口,才可以在初始化过程中得到Spring框架提供内部的资源。

  1. 核心方法
    在 AbstractAutoProxyCreator 代替AnnotationAwareAspectJAutoProxyCreator实现postProcessBeforeInitialization(这里用到了适配器模式)
  2. 依赖知识点
    BeanPostProcessor对Spring容器中所有的bean对象的初始化进行拦截。所以【主要业务类】与【次要业务类】绑定关键点就在下面两个方法中。
    postProcessBeforeInitialization:在bean对象初始化之前
    postProcessAfterInitialization :在bean对象初始化之后,用的是CGLIB代理模式绑定处理。//这里JDK代理需要接口

这里很模糊 断点跟上走走
spring中的BeanPostProcessor
Spring Framework
spring @EnableAspectJAutoProxy背后的那些事(spring AOP源码赏析)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容