文章

SpringAOP

2021.4.17 ・ 共 2176 字,您可能需要 5 分钟阅读

Tags: Spring

Aspect Oriented Programing 面向切面编程

为某一个对象(委托类)准备一个代理(代理类),用来控制对这个对象的访问。

两者有一个共同的父类或父接口。

代理类会对请求做预处理、过滤,将请求分配给指定对象。

  1. 代理类和委托类有相似的行为。
  2. 代理类增强委托类的行为。

代理三要素:以结婚为例

  1. 有共同的行为(结婚)- 定义成接口

    package com.sundingyi.proxy;
    
    /**
     * 定义行为
     */
    public interface Marry {
        public void toMarry();
    }
    
  2. 目标角色(新人)- 实现接口

    package com.sundingyi.proxy;
    
    /**
     * 目标角色 实现行为
     */
    public class You implements Marry {
    @Override
    public void toMarry() {
            System.out.println("you的结婚");
        }
    }
    
  3. 代理角色(婚庆公司) - 实现接口、用户行为的增强

    package com.sundingyi.proxy;
    
    /**
     * 静态代理的代理角色
     * 1. 实现行为
     * 2. 增强行为
     */
    public class MarryCompany implements  Marry{
        // 准备目标对象
        private Marry target;
        // 通过带参构造传递目标对象
        public MarryCompany(Marrytarget) {
            this.target =target;
        }
    
        //1
    @Override
    public void toMarry() {
            // 用户行为增强
            System.out.println("增强方法");
            // 调用目标对象方法
            target.toMarry();
        }
    }
    

执行测试:

package com.sundingyi.proxy;

public class StaticProxy {
    public static void main(String[] args) {
        // 目标对象
        You you = new You();
        // 代理对象
        MarryCompany marryCompany = new MarryCompany(you);
        // 通过代理对象
        marryCompany.toMarry();
    }
}
  1. 目标角色固定
  2. 在应用程序之前就得知了目标角色
  3. 代理对象会增强目标对象的行为
  1. 目标对象不固定。
  2. 在程序运行时动态创建目标对象。
  3. 代理对象会增强目标对象的行为。
package com.sundingyi.proxy;

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

/**
 * JDK 动态代理类
 * 每一个代理类都需要实现 InvocationHandler 接口
 */
public class JdkHandler implements InvocationHandler {
    // 目标对象 不固定, 创建时动态生成
    private Object target;
    // 通过带参构造器传递目标对象
    public JdkHandler(Objecttarget) {
        this.target =target;
    }

    /**
     * 获取代理对象
     * @return
     */
    public Object getProxy() {
        Object object = Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        return object;
    }

    /**
     * 调用目标对象的方法
     * 增强目标对象的行为
     * @param proxy调用该方法的代理实例
     * @param method目标对象的方法
     * @param args目标对象的方法所需要的参数
     * @return
     * @throws Throwable
     */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 用户增强行为(前后都可能会有)
        System.out.println("增强方法");

        // 调用目标对象中的方法(返回 Object)
        Object object =method.invoke(target,args);

        return object;
    }
}

使用

package com.sundingyi.proxy;

public class JdkHandlerTest {
    public static void main(String[] args) {
        // 目标对象
        You you = new You();
        // 代理类
        JdkHandler jdkHandler = new JdkHandler(you);
        // 得到代理对象
        Marry marry = (Marry) jdkHandler.getProxy();
        marry.toMarry();
    }
}

实现原理:继承

<!-- <https://mvnrepository.com/artifact/cglib/cglib> -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
package com.sundingyi.proxy;

import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.tracing.dtrace.ArgsAttributes;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.Method;

public class CglibInterceptor implements MethodInterceptor {
    // 目标对象
    private Object target;
    // 通过构造传入目标对象
    public CglibInterceptor(Object target) {
        this.target = target;
    }
    
    /**
     * 获取代理对象
     * @return
     */
    public Object getProxy(){
        // 通过 Enhancer 中的 create() 生成一个类
        Enhancer enhancer = new Enhancer();
        // 将目标类作为代理类的父类
        enhancer.setSuperclass(target.getClass());
        // 设置拦截器 回调对象为本身
        enhancer.setCallback(this);
        // 生成代理类对象,返回
        return enhancer.create();
    }
    
    /**
     * 拦截器
     * 1. 目标对象的方法调用
     * 2. 行为增强
     * @param o cglib 动态生成的代理类的实例
     * @param method 实体类中所调用的被代理的方法的引用
     * @param objects 参数列表
     * @param methodProxy 生成的代理类对方法的代理引用(代理对象)
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 增强行为
        System.out.println("增强行为");
        // 调用目标类中的方法
        Object object = method.invoke(target, objects);
        return object;
    }
}
package com.sundingyi.proxy;

public class JdkHandlerTest {
    public static void main(String[]args) {
        // 目标对象
        You you = new You();
        // 代理类
        CglibInterceptor cglibInterceptor = new CglibInterceptor(you);
        // 得到代理对象
        Marry marry = (Marry) cglibInterceptor.getProxy();
        marry.toMarry();
    }
}

区别:

  1. JDK 实现接口,Cglib 继承
  2. JDK (目标对象存在接口时)效率高于 Cglib
  3. 如果目标对象有接口实现选择 JDK ,否则选择 Cglib
<!-- <https://mvnrepository.com/artifact/org.aspectj/aspectjweaver> -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.6</version>
    </dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="<http://www.springframework.org/schema/beans>"
       xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
       xmlns:aop="<http://www.springframework.org/schema/aop>"
       xmlns:context="<http://www.springframework.org/schema/context>"
       xsi:schemaLocation="<http://www.springframework.org/schema/beans>
       <http://www.springframework.org/schema/beans/spring-beans.xsd>
       <http://www.springframework.org/schema/context>
       <http://www.springframework.org/schema/context/spring-context.xsd>
       <http://www.springframework.org/schema/aop>
       <http://www.springframework.org/schema/aop/spring-aop.xsd>">

    <context:component-scan base-package="com.sundingyi"/>
<!--    配置AOP自动代理-->
    <aop:aspectj-autoproxy/>
</beans>

切面是切入点和通知的抽象。

切入点:定义拦截哪些类的哪些方法

通知:定义拦截以后要做什么事情

切入点表达式:

  • 执行所有的 public 方法 @Pointcut("execution(* com.sundingyi.service.*.*(..))") 第一个 * 代表了方法的修饰范围,有三个(public private protected) 如果是一个 * 则表示所有范围。所以要这么写: execution(public *(..))

  • 执行所有的set() execution(* *(..))

  • 设置指定包下的任意一个类的任意方法(比如com.xxxx.service) execution(* com.xxxx.service.*.*(..))

  • 设置指定包(service)及子包下的所有方法

    execution(* com.xxxx.service..*.*(..))

package com.sundingyi.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component// 交给 IOC 容器实例化
@Aspect// 声明当前类是一个切面 定义切入点和通知
public class LogCut {

    // 切入点
@Pointcut("execution(* com.sundingyi.service..*.*(..))")
    public void cut() {

    }

    /**
     * 声明前置通知,并将通知应用到指定的切入点上
     * 目标类的方法执行前会执行该通知
     */
@Before(value = "cut()")
    public void before() {
        System.out.println("前置通知");
    }

    /**
     * 声明返回通知,并将通知应用到指定的切入点上
     * 目标类的方法在无异常执行后,执行该通知。
     */
@AfterReturning(value = "cut()")
    public void afterReturn() {
        System.out.println("返回通知");
    }

    /**
     * 声明最终通知,并将通知应用到指定的切入点上
     * 目标类的方法无论是有无异常执行后,该通知都会执行
     */
@After(value = "cut()")
    public void after() {
        System.out.println("after()...");
    }

    /**
     * 声明最终通知,并将通知应用到指定的切入点上
     * 目标类的方法在异常时,执行该通知
     */
@AfterThrowing(value = "cut()")
    public void afterThrow() {
        System.out.println("异常通知...");
    }

    /**
     * 声明环绕通知,并将通知应用到指定的切入点上
     * 目标类的方法执行前后都可以通过环绕通知定义相应的处理
     * 需要通过显式调用方法,否则无法访问指定方法,pjp.proceed()
     */
@Around(value = "cut()")
    public Object around(ProceedingJoinPointpjp) {
        System.out.println("环绕通知-前置通知");

        Object object = null;
        try {
            // 显式调用对应的方法
            object =pjp.proceed();
            System.out.println(pjp.getTarget());
            System.out.println("环绕通知-返回通知");
        } catch (Throwablethrowable) {
throwable.printStackTrace();
            System.out.println("环绕通知异常通知");
        } finally {
            System.out.println("环绕通知-最终通知");
        }
        return object;
    }
}
<aop:config>
  <aop:aspect id="time" ref="timeHandler">
      <aop:pointcut id="addAllMethod" expression="execution(* com.xrq.aop.HelloWorld.*(..))" />
      <aop:before method="printTime" pointcut-ref="addAllMethod" />
      <aop:after method="printTime" pointcut-ref="addAllMethod" />
  </aop:aspect>
</aop:config>