AOP的概述

  • AOP Aspect Oriented Programing 面向切面编程
  • AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视,事务挂历,安全检查,缓存)
  • Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类植入增强代码

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

image.pngimage.png
image.png

AOP的术语

JoinPoint(连接点)
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

Pointcut(切入点)
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义

Advice(通知/增强)
所谓通知是指拦截到Joinpoint之后所要做的事情
通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
#比如在保存之前,做权限校验,就是前置通知
#比如在删除之后,做日志记录,就是后置通知
#前置通知 + 后置通知 = 环绕通知

Introduction(引介)
引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field
#spring不推荐使用

Target(目标对象):
代理的目标对象

Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程
spring采用动态代理织入,而AspectJ采用编译器织入和类装载期织入

Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类

Aspect(切面):
是切入点和通知(引介)的结合

微信图片_20200415122523.png

AOP的底层实现

代码下载:https://gitee.com/chenxiaonian/spring_aop.git


JDK动态代理

只有使用接口的业务类,才可以使用

src/main/java/com/song/aop/demo1/UserDao

  1. package com.song.aop.demo1;
  2. public interface UserDao {
  3. public void save();
  4. public void update();
  5. public void delete();
  6. public void find();
  7. }

src/main/java/com/song/aop/demo1/UserDaoImpl

  1. package com.song.aop.demo1;
  2. public class UserDaoImpl implements UserDao{
  3. public void save() {
  4. System.out.println("保存用户");
  5. }
  6. public void update() {
  7. System.out.println("修改用户");
  8. }
  9. public void delete() {
  10. System.out.println("删除用户");
  11. }
  12. public void find() {
  13. System.out.println("查询用户");
  14. }
  15. }

src/main/java/com/song/aop/demo1/MyJdkProxy

package com.song.aop.demo1;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyJkdProxy implements InvocationHandler {
    private UserDao userDao;

    public MyJkdProxy(UserDao userDao){
        this.userDao = userDao;
    }

    public Object createProxy(){
        Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
        return proxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("save".equals(method.getName())){
            System.out.println("权限校验");
            return method.invoke(userDao,args);
        }
        return method.invoke(userDao,args);
    }
}

src/test/java/com/song/aop/demo1/Demo1Testor

package com.song.sop.demo1;

import com.song.aop.demo1.MyJkdProxy;
import com.song.aop.demo1.UserDao;
import com.song.aop.demo1.UserDaoImpl;
import org.junit.Test;

public class Demo1Testor {
    @Test
    public void test1(){
        UserDao userDao = new UserDaoImpl();

        userDao.save();
        userDao.update();
        userDao.delete();
        userDao.find();
    }

    @Test
    public void test2(){
        UserDao userDao = new UserDaoImpl();
        UserDao proxy = (UserDao)new MyJkdProxy(userDao).createProxy();

        proxy.save();
        proxy.update();
        proxy.delete();
        proxy.find();
    }
}

test1的运行结果:
保存用户
修改用户
删除用户
查询用户

test2的运行结果:
权限校验
保存用户
修改用户
删除用户
查询用户

可以看到test2中的权限校验处理已经被执行了,这就是JDK动态代理的代码实现。其实在Spring中,以上这些JDK动态代理代码,是不需要手写的,只需要配置一下就可以了

CGLIB的动态代理

  • 对于不是用接口的业务类,无法使用JDK动态代理
  • CGlib采用非常底层的字节码技术,可以为一个类创建子类,解决无接口代理问题

pom.xml

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency

src/main/java/com/song/aop/demo2/ProductDao

package com.song.aop.demo2;

public class ProductDao {
    public void save(){
        System.out.println("保存商品");
    }

    public void update(){
        System.out.println("修改商品");
    }

    public void delete(){
        System.out.println("删除商品");
    }

    public void find(){
        System.out.println("查询商品");
    }
}

src/main/java/com/song/aop/demo2/MyCglibProxy

package com.song.aop.demo2;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyCglibProxy implements MethodInterceptor {
    private ProductDao productDao;

    public MyCglibProxy(ProductDao productDao){
        this.productDao = productDao;
    }

    public Object createProxy(){
        // 1. 创建核心类
        Enhancer enhancer = new Enhancer();

        // 2. 设置父类
        enhancer.setSuperclass(productDao.getClass());

        // 3. 设置回调
        enhancer.setCallback(this);

        // 4. 生成代理
        Object proxy = enhancer.create();
        return proxy;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if("save".equals(method.getName())){
            System.out.println("权限校验");
            return methodProxy.invokeSuper(proxy,args);
        }
        return methodProxy.invokeSuper(proxy,args);
    }
}

src/test/java/com/song/aop/demo2/Demo2Testor

package com.song.sop.demo2;

import com.song.aop.demo2.MyCglibProxy;
import com.song.aop.demo2.ProductDao;
import org.junit.Test;

public class Demo2Testor {
    @Test
    public void test1(){
        ProductDao productDao = new ProductDao();

        productDao.save();
        productDao.update();
        productDao.delete();
        productDao.find();
    }

    @Test
    public void test2(){
        ProductDao productDao = new ProductDao();

        ProductDao proxy = (ProductDao)new MyCglibProxy(productDao).createProxy();
        proxy.save();
        proxy.update();
        proxy.delete();
        proxy.find();
    }
}

test1的运行结果:
保存用户
修改用户
删除用户
查询用户

test2的运行结果:
权限校验
保存用户
修改用户
删除用户
查询用户

可以看到test2中的权限校验处理已经被执行了,这就是CGLIB动态代理的代码实现。其实在Spring中,以上这些CGLIB动态代理代码,也是不需要手写的,只需要配置一下就可以了

AOP知识总结

  • Spring在运行期,生成动态代理对象,不需要特殊的编译器
  • Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术,为目标Bean执行横向织入

若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理
若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类

  • 程序中应优先对接口创建代理,便于程序解耦维护
  • 标记为final的方法,不能被代理,会因为无法进行覆盖

JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰
CGLib是针对目标类生产子类,因此类或方法,不能使用final

  • Spring只支持方法连接点,不提供属性连接点

一般切面编程案例

通知类型的介绍

AOP并不是Spring的发明
#AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice

Spring按照通知Advice在目标类方法的连接点位置,可以分为5类

  • 前置通知 org.springframework.aop.MethodBeforeAdvice

在目标方法执行前实施增强

  • 后置通知 org.springframework.aop.AfterReturningAdvice

在目标方法执行后实施增强

  • 环绕通知 org.aopalliance.intercept.MethodInterceptor

在目标方法执行前后实施增强

  • 异常抛出通知 org.springframework.aop.ThrowsAdvice

在方法抛出异常后实施增强

  • 引介通知 org.springframework.aop.IntroductionInterceptor(不建议使用)

在目标类中添加一些新的方法和属性

切面类型的介绍

  • Advisor:代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截
  • PointcutAdvisor:代表具有切点的切面,可以指定拦截目标类哪些方法
  • IntroductionAdvisor:代表引介切面,针对引介通知而使用切面(不建议使用)

一般的切面

  • proxyTargetClass:是否对类代理而不是接口,设置为true时,使用CGLib代理
  • interceptorNames:需要织入目标的Advice
  • singleton:返回代理是否为单实例,默认为单例
  • optimize:当设置为ture时,强制使用CGLib

pom.xml

        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.2</version>
        </dependency>

src/main/resources/applicationContext.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="studentDao" class="com.song.aop.demo3.StudentDaoImpl"/>

    <!--    前置通知类型-->
    <bean id="myBeforeAdvice" class="com.song.aop.demo3.MyBeforeAdvice"/>

    <!--    Spring的AOP产生代理对象-->
    <bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--        配置目标类-->
        <property name="target" ref="studentDao"/>
        <!--        实现的接口-->
        <property name="proxyInterfaces" value="com.song.aop.demo3.StudentDao"/>
        <!--        采用拦截的名称-->
        <property name="interceptorNames" value="myBeforeAdvice"/>
    </bean>
</beans>

src/main/java/com/song/aop/demo3/StudentDao.java

package com.song.aop.demo3;

public interface StudentDao {
    public void find();
    public void save();
    public void update();
    public void delete();
}

src/main/java/com/song/aop/demo3/StudentDaoImpl.java

package com.song.aop.demo3;

public class StudentDaoImpl implements StudentDao{
    @Override
    public void find() {
        System.out.println("学生查询");
    }

    @Override
    public void save() {
        System.out.println("学生保存");
    }

    @Override
    public void update() {
        System.out.println("学生修改");
    }

    @Override
    public void delete() {
        System.out.println("学生删除");
    }
}

src/main/java/com/song/aop/demo3/MyBeforeAdvice.java

package com.song.aop.demo3;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("===== 前置增强 =====");
    }
}

src/test/java/com/song/aop/demo3/Demo3Testor.java

package com.song.sop.demo3;

import com.song.aop.demo3.StudentDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo3Testor {
    @Resource(name = "studentDaoProxy")
    private StudentDao studentDao;

    @Test
    public void test1(){
        studentDao.find();
        studentDao.save();
        studentDao.update();
        studentDao.delete();
    }
}

运行结果:
===== 前置增强 =====
学生查询
===== 前置增强 =====
学生保存
===== 前置增强 =====
学生修改
===== 前置增强 =====
学生删除

这就是一般切面,可以看到StudentDaoImpl类里面的所有方法,都被增强了

带有切入点的切面

  • 使用普通Advice作为切面,将对目标类所有方法进行拦截,不够灵活,在实际开发中常采用带有切点的切面
  • 常用PointcutAdvisor实现类

DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice组合定义切面
JdkRegexpMethodPointcut:构造正则表达式切点

src/main/resources/applicationContext.xml

    <!--    配置目标类-->
    <bean id="customerDao" class="com.song.aop.demo4.CustomerDao"/>

    <!--    配置通知-->
    <bean id="myAroundAdvice" class="com.song.aop.demo4.MyAroundAdvice"/>

    <!--    一般的切面是使用通知作为切面的,因为要对目标类的某个方法进行增强就需要配置一个带有切入点的切面-->
    <bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!--        pattern中配置正则表达式-->
        <!-- <property name="pattern" value=".*save"/> -->
        <property name="patterns" value=".*save,.*update"/>
        <property name="advice" ref="myAroundAdvice"/>
    </bean>

    <!--    配置产生代理-->
    <bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="customerDao"/>
        <property name="proxyTargetClass" value="true"/>
        <property name="interceptorNames" value="myAdvisor"/>
    </bean>

src/main/java/com/song/aop/demo4/CustomerDao.java

package com.song.aop.demo4;

public class CustomerDao {
    public void find(){
        System.out.println("查询客户");
    }

    public void save(){
        System.out.println("保存客户");
    }

    public void update(){
        System.out.println("修改客户");
    }

    public void delete(){
        System.out.println("删除客户");
    }
}

src/main/java/com/song/aop/demo4/MyAroundAdvice.java

package com.song.aop.demo4;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("===== 环绕前增强 ===== ");
        Object proceed = methodInvocation.proceed();
        System.out.println("===== 环绕后增强 ===== ");
        return proceed;
    }
}

src/test/java/com/song/aop/demo4/Demo4Testor.java

package com.song.sop.demo4;

import com.song.aop.demo4.CustomerDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo4Testor {
    @Resource(name = "customerDaoProxy")
    private CustomerDao customerDao;

    @Test
    public void test1(){
        customerDao.find();
        customerDao.save();
        customerDao.delete();
        customerDao.update();
    }
}

运行结果:

查询客户
===== 环绕前增强 =====
保存客户
===== 环绕后增强 =====
删除客户
===== 环绕前增强 =====
修改客户
===== 环绕后增强 =====

传统AOP的自动代理

  • 前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大
  • 解决方案:自动创建代理

BeanNameAutoProxyCreator:根据Bean名称创建代理
DefaultAdvisorAutoProxyCreator:根据Advisor本身包含信息创建代理
AnnotationAwareAspectJAutoProxyCreator:基于Bean中的AspectJ注解进行自动代理

BeanNameAutoProxyCreator

案例:对所有以DAO结尾的Bean所有方法使用代理

src/main/resources/applicationContext.xml

    <bean id="studentDao" class="com.song.aop.demo5.StudentDaoImpl"/>
    <bean id="customerDao" class="com.song.aop.demo5.CustomerDao"/>

    <!--    配置增强-->
    <bean id="myBeforeAdvice" class="com.song.aop.demo5.MyBeforeAdvice"/>
    <bean id="myAroundAdvice" class="com.song.aop.demo5.MyAroundAdvice"/>

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames" value="*Dao"/>
        <property name="interceptorNames" value="myBeforeAdvice"/>
    </bean>

src/main/java/com/song/aop/demo5/CustomerDao.java

package com.song.aop.demo5;

public class CustomerDao {
    public void find(){
        System.out.println("查询客户");
    }

    public void save(){
        System.out.println("保存客户");
    }

    public void update(){
        System.out.println("修改客户");
    }

    public void delete(){
        System.out.println("删除客户");
    }
}

src/main/java/com/song/aop/demo5/Student.java

package com.song.aop.demo5;

public interface StudentDao {
    public void find();
    public void save();
    public void update();
    public void delete();
}

src/main/java/com/song/aop/demo5/StudentImpl.java

package com.song.aop.demo5;

public class StudentDaoImpl implements StudentDao {
    @Override
    public void find() {
        System.out.println("学生查询");
    }

    @Override
    public void save() {
        System.out.println("学生保存");
    }

    @Override
    public void update() {
        System.out.println("学生修改");
    }

    @Override
    public void delete() {
        System.out.println("学生删除");
    }
}

src/main/java/com/song/aop/demo5/MyBeforeAdvice.java

package com.song.aop.demo5;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("===== 前置增强 =====");
    }
}

src/main/java/com/song/aop/demo5/MyAroundAdvice.java

package com.song.aop.demo5;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("===== 环绕前增强 ===== ");
        Object proceed = methodInvocation.proceed();
        System.out.println("===== 环绕后增强 ===== ");
        return proceed;
    }
}

src/test/java/com/song/aop/demo5/Demo5Testor.java

package com.song.sop.demo5;

import com.song.aop.demo5.CustomerDao;
import com.song.aop.demo5.StudentDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo5Testor {
    @Resource(name = "studentDao")
    private StudentDao studentDao;

    @Resource(name = "customerDao")
    private CustomerDao customerDao;

    @Test
    public void demo1(){
        studentDao.find();
        studentDao.save();
        studentDao.update();
        studentDao.delete();

        customerDao.find();
        customerDao.save();
        customerDao.update();
        customerDao.delete();
    }
}

运行结果:

===== 前置增强 =====
学生查询
===== 前置增强 =====
学生保存
===== 前置增强 =====
学生修改
===== 前置增强 =====
学生删除
===== 前置增强 =====
查询客户
===== 前置增强 =====
保存客户
===== 前置增强 =====
修改客户
===== 前置增强 =====
删除客户

DefaultAdvisorAutoProxyCreator

增强类中的某个方法

src/main/resources/applicationContext.xml

    <bean id="studentDao" class="com.song.aop.demo6.StudentDaoImpl"/>
    <bean id="customerDao" class="com.song.aop.demo6.CustomerDao"/>

    <!--    配置增强-->
    <bean id="myBeforeAdvice" class="com.song.aop.demo6.MyBeforeAdvice"/>
    <bean id="myAroundAdvice" class="com.song.aop.demo6.MyAroundAdvice"/>

    <!--    配置切面-->
    <bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="pattern" value="com.song.aop.demo6.CustomerDao.save"/>
        <property name="advice" ref="myAroundAdvice"/>
    </bean>

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

src/test/java/com/song/aop/demo6/Demo6Testor.java

package com.song.sop.demo6;

import com.song.aop.demo6.CustomerDao;
import com.song.aop.demo6.StudentDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo6Testor {
    @Resource(name = "studentDao")
    private StudentDao studentDao;

    @Resource(name = "customerDao")
    private CustomerDao customerDao;

    @Test
    public void test1(){
        studentDao.find();
        studentDao.save();
        studentDao.update();
        studentDao.delete();

        customerDao.find();
        customerDao.save();
        customerDao.update();
        customerDao.delete();
    }
}

运行结果:

学生查询
学生保存
学生修改
学生删除
查询客户
===== 环绕前增强 =====
保存客户
===== 环绕后增强 =====
修改客户
删除客户