浅谈Spring AOP

1. 什么是AOP

AOP(Aspect Oriented Programming) 面向切面编程,是目前软件开发中的一个热点,是Spring框架内容,利用AOP能够对业务逻辑的各个部分隔离,从而使的业务逻辑各部分的耦合性下降,提升程序的可重用性,提高开发效率。java

AOP实现原理是java动态代理,可是jdk的动态代理必须实现接口,因此spring的aop是用cglib这个库实现的,cglib使用里asm这个直接操纵字节码的框架,因此能够作到不使用接口的状况下实现动态代理。正则表达式

2. 应该场景

AOP是处理一些横切行问题。这些横切性问题不会影响到主逻辑的实现,可是会散落到代码的各个部分,难以维护。AOP就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。spring

  • Authentication 权限
  • Caching 缓存
  • Context passing 内容传递
  • Error handling 错误处理
  • Lazy loading 懒加载
  • Debugging  调试
  • logging, tracing, profiling and monitoring 记录跟踪 优化 校准
  • Performance optimization 性能优化
  • Persistence  持久化
  • Resource pooling 资源池
  • Synchronization 同步
  • Transactions 事务

3. AOP与OOP的区别:

OOP面向对象编程,针对业务处理过程的实体及其属性和行为进行抽象封装,以得到更加清晰高效的逻辑单元划分。而AOP则是针对业务处理过程当中的切面进行提取,它所面对的是处理过程的某个步骤或阶段,以得到逻辑过程的中各部分之间低耦合的隔离效果。这两种设计思想在目标上有着本质的差别。编程

经过下面的图能够清晰的理解AOP与OOP的区别:缓存

4. AOP中的概念

  1. AOP代理(AOP Proxy):AOP框架建立的对象,代理就是目标对象的增强。Spring中的AOP代理可使JDK动态代理,也能够是CGLIB代理,前者基于接口,后者基于子类。
  2. Join point(链接点):链接点就是Advice在应用程序上执行的点或时机,表示在程序中明肯定义的点,通常是方法的调用。被拦截到的点,由于Spring只支持方法类型的链接点,因此在Spring中链接点指的就是被拦截到的方法,实际上链接点还能够是字段或者构造器。
  3. Advice(通知):Advice 定义了在 Pointcut里面定义的程序点具体要作的操做,AOP在特定的切入点上执行的加强处理,有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕)。
    • Before:在目标方法被调用以前作加强处理,@Before只须要指定切入点表达式即
    • AfterReturning:在目标方法正常完成后作加强,@AfterReturning除了指定切入点表达式后,还能够指定一个返回值形参名returning,表明目标方法的返回值
    • AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还能够指定一个throwing的返回值形参名,能够经过该形参名来访问目标方法中所抛出的异常对象
    • After:在目标方法完成以后作加强,不管目标方法是否成功完成。@After能够指定一个切入点表达式
    • Around:环绕通知,在目标方法完成先后作加强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
  4. Aspect(切面): Aspect 声明相似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  5. Pointcut(切点):表示一组 joint point,这些 joint point 或是经过逻辑关系组合起来,或是经过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  6. Target(目标对象):织入 Advice 的目标对象。
  7. Weave(织入):将 Aspect 和其余对象链接起来, 并建立 Adviced object 的过程。
  8. Target Object(目标对象): 包含链接点的对象。也被称做被通知或被代理对象。POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans。
  9. introduction(引入):在不修改代码的前提下,引入能够在运行期为类动态地添加一些方法或字段

AOP中的Joinpoint能够有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。也就是说在AOP的概念中咱们能够在上面的这些Joinpoint上织入咱们自定义的Advice,可是在Spring中却没有实现上面全部的joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint性能优化

5. 实战

pom.xmlbash

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.2.4.RELEASE</version>
</dependency>
复制代码

四种实现方式

  1. 经典的基于代理的AOP
  2. @AspectJ注解驱动的切面
  3. 纯POJO切面,经过aop:config标签配置
  4. 注入式AspectJ切面

1. 原生spring实现

定义一个通用接口,全部实现此接口的类都有一个咸鱼方法和一个测试aop的方法app

public interface HelloWorld {
    void saltedFish();

    void testPrintTime();
}
复制代码

实现1框架

public class HelloWorldImpl1 implements HelloWorld{
    @Override
    public void saltedFish() {
        System.out.println("this is a salted fish =========== 1");
    }

    @Override
    public void testPrintTime() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("testPrintTime 1=============111111111");
    }
}
复制代码

实现2ide

public class HelloWorldImpl2 implements HelloWorld{
    @Override
    public void saltedFish() {
        System.out.println("this is a salted fish =========== 2");
    }

    @Override
    public void testPrintTime() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("testPrintTime 2=============22222222");
    }
}
复制代码

定义一个Advice,实如今链接点以前以后该干的事

public class TimeHandler implements MethodBeforeAdvice, AfterReturningAdvice {
    Long before = 0L;

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        Long after = System.currentTimeMillis();
        System.out.println("==========代理后time, " + after + " ======= 间隔: " + (after - before) + "==========");
    }

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        before = System.currentTimeMillis();
        System.out.println("==========代理前time:" + before + "===========");
    }
}
复制代码

经过xml配置切面

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 定义 -->
    <bean id="h1" class="com.example.demo.aop.HelloWorldImpl1"></bean>
    <bean id="h2" class="com.example.demo.aop.HelloWorldImpl2"></bean>

    <!--  定义advice -->
    <bean id="timeHandler" class="com.example.demo.aop.TimeHandler"></bean>

    <!--  定义point cut  -->
    <bean id="timePointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*testPrintTime"></property>
    </bean>

    <!-- 切面 关联切入点与通知 -->
    <bean id="timeHandlerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="timeHandler"></property>
        <property name="pointcut" ref="timePointCut"></property>
    </bean>

    <!-- 设置代理-->
    <bean id="proxy1" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--  代理的对象 -->
        <property name="target" ref="h1"></property>
        <!-- 使用的切面 -->
        <property name="interceptorNames" value="timeHandlerAdvisor"></property>
        <!-- 代理接口 -->
        <property name="interfaces" value="com.example.demo.aop.HelloWorld"></property>
    </bean>

    <!-- 设置代理-->
    <bean id="proxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--  代理的对象 -->
        <property name="target" ref="h2"></property>
        <!-- 使用的切面 -->
        <property name="interceptorNames" value="timeHandlerAdvisor"></property>
        <!-- 代理接口 -->
        <property name="interfaces" value="com.example.demo.aop.HelloWorld"></property>
    </bean>
</beans>
复制代码

测试类

public class AOPTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("./application.xml");
        HelloWorld helloWorld1 = (HelloWorld) applicationContext.getBean("proxy1");
        HelloWorld helloWorld2 = (HelloWorld) applicationContext.getBean("proxy2");

        helloWorld1.saltedFish();
        System.out.println("---------------------");
        helloWorld1.testPrintTime();

        System.out.println("=======================");

        helloWorld2.saltedFish();
        System.out.println("---------------------");
        helloWorld2.testPrintTime();
    }
}
复制代码

打印

this is a salted fish =========== 1
---------------------
==========代理前time:1582477901748===========
testPrintTime 1=============111111111
==========代理后time, 1582477902750 ======= 间隔: 1002==========
=======================
this is a salted fish =========== 2
---------------------
==========代理前time:1582477902750===========
testPrintTime 2=============22222222
==========代理后time, 1582477903250 ======= 间隔: 500==========
复制代码
复制代码