Spring入门到进阶 - Spring AOP

Posted GreyBig

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring入门到进阶 - Spring AOP相关的知识,希望对你有一定的参考价值。

AOP概述

AOP : Aspect Oriented Programing 面向切面编程

AOP采用横向抽取机制(代理机制), 取代了传统纵向继承体系重复性代码在性能监视, 事务管理, 安全检查, 缓存中使用

Spring AOP是使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码

什么是AOP?

面向切面编程,是oop(面向对象编程)的补充和完善,oop是纵向的继承结构来代表公共行为的集合,当需要引入另外一个与业务无关的松散的行为的话就很无力,比如日志系统,所有的对象系统都需要日志,以往的做法会产生大量的重复代码,耦合度提高,重用性降低,显然是不合适的。

aop用横向的代理机制将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性

AOP相关术语

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

Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义;个人理解:一个要拦截或者已经被拦截的方法被称为一个切入点。

Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。个人理解:对方法进行拦截之后所做的增强方法就是通知,分为前置通知,后置通知,异常通知,最终通知,环绕通知。
Target(目标对象)代理的目标对象

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

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

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

AOP的底层原理(实现)

JDK的动态代理

public interface UserDao {
    public void save();
    public void update();
    public void delete();
    public void find();
}
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("保存用户...");
    }
    public void update() {
        System.out.println("修改用户...");
    }
    public void delete() {
        System.out.println("删除用户...");
    }
    public void find() {
        System.out.println("查询用户...");
    }
}

/**
 * JDK的动态代理
 * 动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。
 * 该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类
 */
public class MyJdkProxy implements InvocationHandler {

    // 目标对象
    private final UserDao userDao;

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

    // 绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
    public Object createProxy() {
        //该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
        //第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
        //第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
        //第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
        //根据传入的目标返回一个代理对象
        return Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
    }

    //关联的这个实现类的方法被调用时将被执行
    /*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/
    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);
    }
}

// 测试类
public class SpringDemo1 {
    @Test
    public void demo1() {
        UserDao userDao = new UserDaoImpl();
        UserDao proxy = (UserDao) new MyJdkProxy(userDao).createProxy();
        proxy.save();
        proxy.update();
        proxy.delete();
        proxy.find();
    }
}

CGLIB的动态代理

  • 使用场景:对于不使用接口的业务类,无法使用JDK动态代理

  • CGlib采用非常底层字节码技术,可以为一个类动态的增加一些方法也可以生成一个类去继承这个类,解决无接口代理问题

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("查询商品...");}
}

// Cglib实现
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;
    }

    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);
    }
}

// 测试类
public class SpringDemo2 {
    @Test
    public void demo1() {

        ProductDao productDao = new ProductDao();

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

代理知识总结

  • Spring在运行期,生成动态代理对象,不需要特殊的编译器

  • Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术 为目标Bean执行横向织入

    1. 若目标对象实现了若干接口,spring使用JDK的动态代理

    2. 若目标对象没有实现任何接口,spring使用CGLIB动态代理

Spring的AOP的通知类型

1、AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Adive

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

  1. 前置通知:在目标方法执行前实施增强
  2. 后置通知:在目标方法执行后实施增强
  3. 环绕通知:在目标方法执行前后实施增强
  4. 异常通知:在方法抛出异常后实施增强
  5. (Spring只支持方法通知)引介通知:在目标类中添加一些新的方法和属性

Spring的AOP的切面

Spring的AOP的切面类型

Advisor:一般切面(拦截目标类所有方法)
PointcutAdvisor:代表具有切点的切面(拦截目标类指定方法)
IntroductionAdvisor:引介切面(不要求掌握)

Advisor(一般切面的实现)

准备工作:

1、引入AOP的两个包:
aopalliance
spring-aop

2、配置目标类

 <bean id="studentDao" class="com.imooc.aop.demo3.StudentDaoImpl"/>

开始配置:

1、编写增强类,如

public class MyBeforeAdvice implements MethodBeforeAdvice {}

2、配置到xml

<bean id="myBeforeAdvice" class="com.imooc.aop.demo3.MyBeforeAdvice"/>

3、配置增强
ProxyFactoryBean常用可配置属性

  • target:代理的目标对象

  • proxyInterfaces:代理要实现的接口
    注意:如果多个接口可以使用list赋值

<list>
<value></value>
</list>

代码:

<!--Spring的AOP 产生代理对象-->
    <bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--配置目标类-->
        <property name="target" ref="studentDao"/>
        <!--实现的接口-->
        <property name="proxyInterfaces" value="com.imooc.aop.demo3.StudentDao"/>
        <!--采用拦截的名称-->
        <property name="interceptorNames" value="myBeforeAdvice"/>
        <!--当设置为ture时,强制使用CGLib代理-->
        <property name="optimize" value="true"></property>
    </bean>

在测试类注入代理类:

@Resource(name="studentDaoProxy")
private StudentDao studentDao;
public void demo1(){
	studentDao.find();
}

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

PointcutAdvisor:代表具有切点的切面实现(拦截目标类指定方法)

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

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

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

<!--配置产生代理-->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="customerDao"></property>
<property name="proxyTargetClass" value="true"></property>
<property name="interceptorNames" value="myAdivsor"></property>
</bean>
// 目标类
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("删除客户...");
    }
}
// 通知类
public class MyAroundAdvice implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("环绕前增强===================");
        Object obj = invocation.proceed();
        System.out.println("环绕后增强===================");
        return obj;
    }
}

Spring的传统AOP的动态代理

自动代理创建

前面的案例中,每个代理都是通过ProxyFacaoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大

解决办法:自动创建代理

  • BeanNameAutoProxyCreator 根据Bean名称创建代理

  • DefaultAdvisorAutoProxyCreator 根据Advisor本身包含信息创建代理

  • AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ注解进行自动代理

基于Bean名称创建代理 (BeanNameAutoProxyCreator )

<!--配置目标类-->
<bean id="studentDao" class="com.imooc.aop.advisor.auto1.StudentDaoImpl"/>
<bean id="customerDao" class="com.imooc.aop.advisor.auto1.CustomerDao"/>
<bean id="testDao" class="com.imooc.aop.advisor.auto1.TestDao"/>

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

<!-- 自动创建代理之基于Bean名称来增强的方法,缺点是只能对所有类的所有方法增强 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="*Dao"/>
    <property name="interceptorNames" value="myBeforeAdvice"/>
</bean>

基于切面信息的自动代理 (DefaultAdvisorAutoProxyCreator)

<!--配置目标类-->
<bean id="studentDao" class="com.imooc.aop.advisor.auto2.StudentDaoImpl"/>
<bean id="customerDao" class="com.imooc.aop.advisor.auto2.CustomerDao"/>
<bean id="testDao" class="com.imooc.aop.advisor.auto1.TestDao"/>

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

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

<!-- 基于切面信息的自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>











以上是关于Spring入门到进阶 - Spring AOP的主要内容,如果未能解决你的问题,请参考以下文章

❤️‍Spring从入门到大神--AOP从入门到精通

Java之Spring AOP入门到精通IDEA版(一篇文章精通系列)

Spring框架进阶Spring V2.0 AOP

《Java从入门到放弃》入门篇:spring中AOP的配置方式

Spring框架进阶Spring V3.0 AOP源码分析流程

AOP源码解析---Spring源码从入门到精通(十九)