AOP简单使用


AOP简单使用

AOP术语

  • 通知(Advice)
名称 作用
前置通知 在目标方法调用之前执行通知功能
后置通知 在目标方法调用之后执行通知功能
返回通知 在目标成功执行之后调用通知
异常通知 在目标返回抛出异常后调用通知
环绕通知 通知包裹被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
  • 连接点
    连接点是应用执行过程中能够插入切面的一个点。切面代码可以利用这些点将切面插入到正常流程中

  • 切点
    切点的定义会匹配通知所要织入的一个或多个连接点。通常用明确的类或方法名称或者是正则表达式定义所匹配的类和方法来指定切点

  • 切面
    切面是通知和切点的结合。通知和切点定义了切面的全部内容

  • 引入
    引入允许我们向现有的类添加新方法或属性

  • 织入
    织入是把切面应用到目标对象并创建新的代理对象的过程。织入可以在编译器类加载期运行期

AOP示例

  • 切点
java
@Pointcut("execution(* com.example.springdemo.interfaces.impl.*.*(..))")
public void performance(){
}
  • 前置通知
java
@Before("performance()")
public void silenceCellPhones(){
    log.info("Silencing cell phone");
}
  • 后置通知
java
@Before("performance()")
public void silenceCellPhones(){
    log.info("Silencing cell phone");
}

-环绕通知

java
@Around("performance()")
public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
    //执行目标方法
    joinPoint.proceed();
}

-返回通知

java
@AfterReturning("performance()")
public void applause(){
    log.info("CLAP CLAP CLAP");
}

-异常通知

java
@AfterThrowing("performance()")
public void demandRefund(){
    log.info("Demanding a refund");
}

增强类型

前置增强

  • 接口org.springframework.aop.MethodBeforeAdvice

后置增强

  • 接口org.springframework.aop.AfterReturningAdvice

环绕增强

  • 接口org.springframework.aop.MethodMatcher

异常增强

  • 接口org.springframework.aop.ThrowsAdvice

  • Advice*Impl

java
public class AfterAdviceImpl implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(this.getClass().getSimpleName() + " 执行后置增强 : " + args);
    }
}

public class AroundAdviceImpl implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object[] args = invocation.getArguments();
        System.out.println(this.getClass().getSimpleName() + " around advice: before " + args);
        Object obj = invocation.proceed();
        System.out.println(this.getClass().getSimpleName() + "around advice: after " + args);
        return obj;
    }
}

public class BeforeAdviceImpl implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(this.getClass().getSimpleName() + " 执行前置增强 : " + args);
    }
}

public class ExceptionAdviceImpl implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
            throws Throwable {
        System.out.println("------");
        System.out.println(this.getClass().getSimpleName() + " throws exception, method=" + method.getName());
        System.out.println(this.getClass().getSimpleName() + "throws exception, message=" + ex.getMessage());
    }
}
  • ServiceTest.java
java
public class ServiceTest {
    public void execute(){
        System.out.println(this.getClass().getSimpleName() + " : execute");
    }
}
  • 执行主函数
java
public class AdviceMainApp {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(new ServiceTest());

        factory.addAdvice(new BeforeAdviceImpl());
        factory.addAdvice(new AroundAdviceImpl());
        factory.addAdvice(new AfterAdviceImpl());
        factory.addAdvice(new ExceptionAdviceImpl());

        ServiceTest serviceTest = (ServiceTest) factory.getProxy();

        serviceTest.execute();
    }
}
  • 执行结果
    8F5eDU.png
    8F5eDU.png

通过ProxyFactory通过setTarget()设置目标对象,通过addAdvice添加增强器形成增强链,在代理类执行方法时会执行增强器

切点表达式

spring aop中只是用到AspectJ语法中的一个子集

切点表达式函数

类型 函数 说明
方法切点函数 execution(),@annotation() 通过描述目标方法信息定义连接点
方法入参切点函数 args(),@args() 通过描述目标类方法入参的信息定义连接点
目标类切点函数 within(),target(),@within(),@target() 通过描述目标类类型信息定义连接点
代理类切点函数 this() 通过描述目标类的代理类的信息定义连接点

函数说明

函数 说明
execution() 表示满足某一匹配模式的所有目标类方法连接点,如execution(* handle(…))表示所有目标类中的handle()方法
@annotation() 表示标注了特定注解的目标方法连接点,如@annotation(com.aspectj.Authority)表示任何标注了@Authority注解的目标类方法
args() 通过判别目标类方法运行时入参对象的类型定义指定连接点,如args(com.data.Car)表示所有有且仅有一个按类型匹配于Car(含子类)入参的方法
@args() 通过判别目标方法运行时入参对象的类是否标注特定注解来制定连接点,如@args(com.aspectj.Authority)表示任何这样的一个目标方法:它有一个入参且入参对象的类标注@Authority注解。要使@args()生效,类继承树中,标注注解的类类型需要不高于入参类类型
within 表示特定域下的所有连接点,如within(com.service.*),within(com.service.*Service)和within(com.service…*)
target() 假如目标按类型匹配于指定类,则目标类的所有连接点匹配这个切点。如通过target(com.data.Car)定义的切点,Car及Car的子类中的所有连接点都匹配该切点,包括子类中扩展的方法
@within() 假如目标类按类型匹配于某个类A,且类A标注了特定注解,则目标类的所有连接点都匹配于这个切点。如@within(com.aspectj.Authority)定义的切点,假如Car类标注了@Authority注解,则Car以及Car的子类的所有连接点都匹配。@within标注接口类无效
@target() 目标类标注了特定注解,则目标类(不包括子类)所有连接点都匹配该切点。如通过@target(com.aspectj.Authority)定义的切点,若BMWCar标注了@Authority,则BMWCar所有连接点匹配该切点
this() 代理类按类型匹配于指定类,则被代理的目标类所有连接点匹配切点
bean(beanName) 这个是Spring特有的表示beanName的bean

通配符

类型 说明
* 匹配任意字符,但只能匹配上下文中的一个元素
匹配任意字符,可以匹配上下文中的多个元素。表示类时,和*联合使用;表示入参时单独使用
+ 按类型匹配指定类的所有类(包括实现类和继承类),必须跟在类名后面
  • 支持所有通配符:execution(),within()
  • 仅支持+通配符:args(),this(),target()
  • 不支持通配符:@args,@within,@target,@annotation

execution()语法

  • 语法:execution(<修饰符模式>? <返回类型模式> <方法名模式> (<参数模式>) <异常模式>?)

通过方法签名定义切点

  • execution(pulic * *(…)):匹配目标类的public方法,第一个*代表返回类型,第二个*代表方法名,…代表任意入参
  • execution(* *To(…)):匹配目标类所有以To结尾的方法,第一个*代表返回类型,*To代表任意以To结尾的方法

通过类定义切点

  • execution(* com.data.User.*(…)):匹配User接口的所有方法
  • execution(* com.data.User+.*(…)):匹配User接口的所有方法,包括其实现类中不在User接口中定义的方法

通过类包定义切点

  • execution(* com.data.*(…)):匹配data包下所有类的所有方法
  • execution(* com.data.User…*(…)):匹配data包及其子孙包中的所有类的所有方法
  • execution(* com…*Manager.get*(…)):匹配com包及其子孙包中后缀为Manager的类里以get开头的方法

通过方法入参定义切点

  • execution(* get(String, int)):匹配get(String, int)方法
  • execution(* get(String, *)):匹配名为get且第一个入参类型为String、第二个入参类型任意的方法
  • execution(* get(String, …)):匹配名为get且第一个入参为String类型的方法
  • execution(* get(Object+)):匹配名为get且唯一入参是Object或其子类的方法

逻辑运算符

  • 与&&,或||,非!

织入顺序

  • 如果增强在同一个切面类中声明,则依照增强在切面类中定义的顺序织入
  • 如果增强位于不同的增强类中,且都实现了org.springframework.core.Ordered接口,则由接口方法的顺序号决定(顺序号小的先织入)
  • 如果增强位于不同的增强类中,且没有实现org.springframework.core.Ordered接口,织入顺序不确定

获取连接点信息

  • **args()用于绑定连接点方法的入参,@annotation()用于绑定连接点方法的注解对象,@args()**用于绑定连接点方法的入参注解。下例表示方法入参为(String, int, …)的方法匹配该切点,并将name和age两个参数绑定到切面方法的入参中
  • AspectJ使用org.aspectj.lang.JointPoint接口表示目标类连接点对象。如果是环绕增强时,使用org.aspectj.lang.ProceedingJointPoint表示连接点对象,该类是JointPoint接口的子接口。任何一个增强方法都可以通过将第一个入参声明为JointPoint访问到连接点上下文的信息
  • 使用**this()target()**可以绑定被代理对象的实例。下例表示代理对象为User类的所有方法匹配该切点,且代理对象绑定到user入参中
  • **@within()**和@target()函数可以将目标类的注解对象绑定到增强方法中
  • 通过returning绑定连接点方法的返回值
  • 使用AfterThrowing注解的throwing成员绑定

动态代理的两种方式

  • JDK中的动态代理是动态创建一个具有某种接口能力的代理对象。这样就必须要有Interface并且方法必须是publicpublic final

  • CGLib采用的是底层的字节码技术,通过子类去拦截父类的方法,织入横切逻辑;不能代理finalprivate

  • 演示代码

  • TargetService.java

java
interface TargetService {
    public void process(String param);
}
  • TargetServiceImpl.java
java
public class TargetServiceImpl implements TargetService {
    @Override
    public void process(String param) {
        System.out.println(this.getClass().getSimpleName() + ":" + param);
    }
}
  • CglibProxy.java
java
public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class targetClass){
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println(this.getClass().getSimpleName() + " : before execute" );
        Object obj = methodProxy.invokeSuper(o,objects);
        System.out.println(this.getClass().getSimpleName() + " : before after" );
        return obj;
    }
}
  • JdkProxy.java
java
public class JdkProxy implements InvocationHandler {

    private Object targer;

    public JdkProxy(Object targer){
        this.targer = targer;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(this.getClass().getSimpleName() + " : before execute" );
        Object obj = method.invoke(targer,args);
        System.out.println(this.getClass().getSimpleName() + " : before after" );
        return obj;
    }
}
  • MainApp.java
java
public class MainApp {
    public static void main(String[] args) {
        //JDK proxy
        TargetServiceImpl jdkService = new TargetServiceImpl();
        JdkProxy jdkProxy = new JdkProxy(jdkService);
        TargetService jdkServiceProxy = (TargetService) Proxy.newProxyInstance(jdkService.getClass().getClassLoader(),jdkService.getClass().getInterfaces(),jdkProxy);
        jdkServiceProxy.process("jdk 动态代理");

        //CGLib proxy
        CglibProxy cglibProxy = new CglibProxy();
        TargetService cglibServiceProxy = (TargetService) cglibProxy.getProxy(TargetServiceImpl.class);
        cglibServiceProxy.process("CGLib 动态代理");
    }
}
  • 运行结果

8Fdk5Q.png
8Fdk5Q.png

从结果可以看出CGLib是生成动态代理的内部类进行方法的拦截,从而达到增强的目的。jdk的动态代理是传入目标类对象通过反射去执行目标类对象的方法从而到达执行增强的目的。

参考资料