Spring Core 官方文档阅读笔记(九)

  1. AOP常见概念
  • Aspect:切面,即关注点的模块化。一个切面定义了位于横切点的处理逻辑。可以将一整套业务逻辑看成是一条处理链,当需要在某个处理的节点之前或者之后增加自定义处理的时候,可以在节点上或下切入一个平面,所有调用该处理节点的逻辑,都被切入,那么所有调用这个节点的处理都会先执行自定义的逻辑。不需要修改原有的业务代码。可以通过@Aspect来声明一个切面
  • Joint Point:连接点,即切面介入的方法节点。比如说某一个处理链上有5个方法节点,这五个节点上都可以切入切面,那么就有5个连接点。
  • Advice:通知,即切面需要完成的处理,以及什么时候使用切面的处理。spring AOP有五种通知类型:前置通知(Before)、后置通知(After)、返回通知(After-Returning)、异常通知(After-Throwing)、环绕通知(Around)。
  • Pointcut:切入点,在连接点的基础之上,如果切面切入了某个方法节点,则称该节点为切入点。
  • Introduction:引入,即为一个类增加一个新的方法。比如A类里只有一个方法,现在想要A类中拥有B类的方法,可以使用Introduction来实现。
  • Target Object:即被AOP增强的对象
  • AOP Proxy:Spring为实现切面而创建的对象。Spring AOP代理有两类:JDK动态代理和CGLIB代理
  • Weaving:将切面与其他应用程序类型或者对象链接,以创建通知对象。
  1. execution表达式
    execution表达式用于匹配需要需要切入的方法,语法如下
    execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
    如:
execution(* com.loongshawn.method.ces..*.*(..))
标识符 含义
execution 表达式主体
第一个*号 返回值类型,*号代表任意类型
com.loongshawn.method.ces AOP所切的包名
包名后的".." 表示当前包及子包
第二个* 类名,*号表示所有类
.*(..) 方法名及参数,*号代表任意方法名,括号内是参数,..表示任意参数

execution中除了返回类型模式,方法名模式和参数模式外,其他的都是可选的。
execution中的+号代表所标记类的所有子类,如Object+,就代表Object及其所有的子类。

  1. 切入点
    可以使用@Pointcut声明一个切入点,如
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

支持的切入点指示符有:

  • execution:用于匹配方法的切入点。如下代码:
public interface MyInterface {

    void sayHello();
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:51 2019/4/3
 * @Description:
 * @modified:
 */
public class MyInterfaceImpl implements MyInterface {
    @Override
    public void sayHello() {
        System.out.println("AOP真牛逼!");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:43 2019/4/3
 * @Description:
 * @modified:
 */
@Aspect
public class MyExecution {

    @Pointcut("execution(* configure.pointcut.execution.MyInterface+.*(..))")
    private void myExecution(){
    }

    @Before("myExecution()")
    private void before(JoinPoint joinPoint) {
        System.out.println("对,就是辣么牛逼!");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:59 2019/4/3
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyInterface myInterface() {
        return new MyInterfaceImpl();
    }

    @Bean
    public MyExecution myExecution() {
        return new MyExecution();
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 16:00 2019/4/3
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        MyInterface myInterfaceImpl = acac.getBean(MyInterface.class);
        myInterfaceImpl.sayHello();
    }
}

输出结果:

四月 03, 2019 5:08:46 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Wed Apr 03 17:08:46 CST 2019]; root of context hierarchy
AOP牛不牛逼?
AOP真牛逼!

在编写上面代码的时候,遇到了如下问题:
@Pointcut的execution一开始写成了configure.pointcut.execution.MyInterface.(..),跑起来报错,后来改成configure.pointcut.execution.MyInterfaceImpl.(..),一样不行,后来查资料,发现需要写成@Pointcut("execution(* configure.pointcut.execution.MyInterface+.*(..))"),+号表示MyInterface下面的所有子类。

  • within
    用于匹配特定类型内的方法。
/**
 * @author kuromaru
 */
public interface MyWithinInterface {

    void say();
}
/**
 * @Author: kuromaru
 * @Date: Created in 9:34 2019/4/4
 * @Description:
 * @modified:
 */
public class MyWithinInterfaceImpl implements MyWithinInterface {
    @Override
    public void say() {
        System.out.println("AOP真牛逼!");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 9:35 2019/4/4
 * @Description:
 * @modified:
 */
public class MyWithinInterfaceImpl2 implements MyWithinInterface {
    @Override
    public void say() {
        System.out.println("对,AOP真牛逼!");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:43 2019/4/3
 * @Description:
 * @modified:
 */
@Aspect
public class MyExecution {


    @Pointcut("within(configure.pointcut.within.MyWithinInterface+))")
    private void myExecution(){
    }

    @Before("myExecution()")
    private void before(JoinPoint joinPoint) {
        System.out.println("AOP牛不牛逼?");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:59 2019/4/3
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean(name = "myWithinInterface")
    public MyWithinInterface myWithinInterface() {
        return new MyWithinInterfaceImpl();
    }

    @Bean(name = "myWithinInterface2")
    public MyWithinInterface myWithinInterface2() {
        return new MyWithinInterfaceImpl2();
    }

    @Bean
    public MyExecution myExecution() {
        return new MyExecution();
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 16:00 2019/4/3
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        MyWithinInterface myInterfaceImpl = (MyWithinInterface) acac.getBean("myWithinInterface");
        MyWithinInterface myInterfaceImpl2 = (MyWithinInterface) acac.getBean("myWithinInterface2");
        myInterfaceImpl.say();
        myInterfaceImpl2.say();
    }
}

四月 04, 2019 10:03:49 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 10:03:49 CST 2019]; root of context hierarchy
AOP牛不牛逼?
AOP真牛逼!
AOP牛不牛逼?
对,AOP真牛逼!

  • this
    this匹配的是生成的代理类的类型。举个栗子:
public interface MyInterface {

    void sayHello();
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:51 2019/4/3
 * @Description:
 * @modified:
 */
public class MyInterfaceImpl implements MyInterface {

    @Autowired
    private MyInvoke myInvoke;

    @Override
    public void sayHello() {
        myInvoke.invoke();
        System.out.println("AOP真牛逼!");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 10:29 2019/4/4
 * @Description:
 * @modified:
 */
public class MyInvoke {

    public void invoke() {
        System.out.println("我去~");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:43 2019/4/3
 * @Description:
 * @modified:
 */
@Aspect
public class MyExecution {

    @Pointcut("this(configure.pointcut.thethis.MyInterface)")
    private void myExecution(){
    }

    @Before("myExecution()")
    private void before(JoinPoint joinPoint) {
        System.out.println("AOP牛不牛逼?");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:59 2019/4/3
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyInterface myInterface() {
        return new MyInterfaceImpl();
    }

    @Bean
    public MyInvoke myInvoke() {
        return new MyInvoke();
    }

    @Bean
    public MyExecution myExecution() {
        return new MyExecution();
    }

}
/**
 * @Author: kuromaru
 * @Date: Created in 16:00 2019/4/3
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        MyInterface myInterfaceImpl = acac.getBean(MyInterface.class);
        myInterfaceImpl.sayHello();
    }
}

输出结果:

四月 04, 2019 1:16:50 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 13:16:50 CST 2019]; root of context hierarchy
AOP牛不牛逼?
我去~
AOP真牛逼!

我们知道,JDK代理需要有接口,而CGLIB代理可以是任何类。那么this的匹配逻辑似乎是当需要被切入点的类是接口时,默认使用JDK代理,如图:

所以,this所匹配的类就是这个代理类。但是,如果我们把切入点换成MyInterfaceImpl,就会有问题,如下:

/**
 * @Author: kuromaru
 * @Date: Created in 15:43 2019/4/3
 * @Description:
 * @modified:
 */
@Aspect
public class MyExecution {

    @Pointcut("this(configure.pointcut.thethis.MyInterfaceImpl)")
    private void myExecution(){
    }

    @Before("myExecution()")
    private void before(JoinPoint joinPoint) {
        System.out.println("AOP牛不牛逼?");
    }
}

输出结果:

四月 04, 2019 1:23:23 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 13:23:23 CST 2019]; root of context hierarchy
我去~
AOP真牛逼!

根据this匹配类的逻辑,由于切点是实现类,所以this所匹配的类也是CGLIB生成的代理类,但是由于MyInterfaceImpl实现了接口MyInterface,spring为其生成了JDK代理类,所以实际上使用的是JDK的代理类。由于所有的增强处理都织入到了CGLIB的代理类上,所以JDK的代理类只有被代理的方法输出,没有执行before里的逻辑。

我们可以通过强制使用CGLIB代理来达到我们想要的效果,代码如下(只列出需要改动的类和文件):

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config proxy-target-class="true" />
</beans>
/**
 * @Author: kuromaru
 * @Date: Created in 15:59 2019/4/3
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
@ImportResource(value = "file:/thethis/aop-proxy.xml")
public class MyConfig {

    @Bean
    public MyInterface myInterface() {
        return new MyInterfaceImpl();
    }

    @Bean
    public MyInvoke myInvoke() {
        return new MyInvoke();
    }

    @Bean
    public MyExecution myExecution() {
        return new MyExecution();
    }

}

输出结果:

四月 04, 2019 1:43:02 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 13:43:02 CST 2019]; root of context hierarchy
四月 04, 2019 1:43:02 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from URL [file:F:/workspace/svn/trunk/spring-core-study/src/main/java/configure/pointcut/thethis/aop-proxy.xml]
AOP牛不牛逼?
我去~
AOP真牛逼!

this和target的不同之处也在于此,target匹配的是被代理的类,即MyInterface和MyInterfaceImpl,所以,如果使用target的参数里不管是接口还是实现类,都会正常执行before里的逻辑。target的使用会在下面整理。

  • target
    匹配的即为上面阐述过的被代理的类。把上面的代码换成target,如下:
/**
 * @Author: kuromaru
 * @Date: Created in 15:43 2019/4/3
 * @Description:
 * @modified:
 */
@Aspect
public class MyExecution {

    @Pointcut("target(configure.pointcut.target.MyInterfaceImpl)")
    private void myExecution(){
    }

    @Before("myExecution()")
    private void before(JoinPoint joinPoint) {
        System.out.println("AOP牛不牛逼?");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:59 2019/4/3
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyInterface myInterface() {
        return new MyInterfaceImpl();
    }

    @Bean
    public MyInvoke myInvoke() {
        return new MyInvoke();
    }

    @Bean
    public MyExecution myExecution() {
        return new MyExecution();
    }

}

输出结果:

四月 04, 2019 1:48:33 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 13:48:33 CST 2019]; root of context hierarchy
AOP牛不牛逼?
我去~
AOP真牛逼!

把target参数中的MyInterfaceIml换成MyInterface也是一样的结果。

  • args
    用于匹配方法参数是指定的类型的方法。show you the code:
/**
 * @Author: kuromaru
 * @Date: Created in 13:54 2019/4/4
 * @Description:
 * @modified:
 */
public class MyMethod {

    public void showArgName(ArgsType argsType) {
        System.out.println(argsType.getName());
    }

    public void showArgName(String name) {
        ArgsType argsType = new ArgsType(){{
            setName(name);
        }};
        System.out.println(argsType.getName());
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:53 2019/4/4
 * @Description:
 * @modified:
 */
public class ArgsType {

    private String name;

    private String value;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:56 2019/4/4
 * @Description:
 * @modified:
 */
@Aspect
public class MyExecution {

    @Pointcut("args(ArgsType)")
    private void myExecution() {

    }

    @Before("myExecution()")
    private void before(JoinPoint joinPoint) {
        System.out.println("马上打印" + joinPoint.getTarget().toString() + "的参数");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:59 2019/4/3
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyMethod myMethod() {
        return new MyMethod();
    }

    @Bean
    public MyExecution myExecution() {
        return new MyExecution();
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 16:00 2019/4/3
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        MyMethod myMethod = acac.getBean(MyMethod.class);
        ArgsType argsType = new ArgsType(){{
           setName("paramA");
           setValue("1");
        }};
        System.out.println("传入ArgsType");
        myMethod.showArgName(argsType);
        System.out.println("传入String");
        myMethod.showArgName("paramB");
    }
}

输出结果:

四月 04, 2019 2:03:49 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 14:03:49 CST 2019]; root of context hierarchy
传入ArgsType
马上打印configure.pointcut.args.MyMethod@477b4cdf的参数
paramA
传入String
paramB

  • @target
    用于匹配对象上有特定注解的类,如下
/**
 * @Author: kuromaru
 * @Date: Created in 14:16 2019/4/4
 * @Description:
 * @modified:
 */
@MyAnnotation
public class MyMethod {

    public void run() {
        System.out.println("向前跑");
    }

    public void jump() {
        System.out.println("向上跳");
    }

    public static void distory() {
        System.out.println("毁了你丫的");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 14:19 2019/4/4
 * @Description:
 * @modified:
 */
@Aspect
public class MyExecution {

    @Pointcut("@target(MyAnnotation)")
    private void pointcut() {}

    @After("pointcut()")
    private void after() {
        System.out.println("你咋不上天呢");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 14:20 2019/4/4
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyMethod myMethod() {
        return new MyMethod();
    }

    @Bean
    public MyExecution myExecution() {
        return new MyExecution();
    }
}
@Target({ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation {
}
/**
 * @Author: kuromaru
 * @Date: Created in 14:21 2019/4/4
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        MyMethod myMethod = acac.getBean(MyMethod.class);
        myMethod.jump();
        System.out.println("=======================华丽的分割线===========================");
        myMethod.run();
        System.out.println("=======================华丽的分割线===========================");
        MyMethod.distory();
    }
}

输出结果:

四月 04, 2019 3:10:23 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 15:10:23 CST 2019]; root of context hierarchy
向上跳
你咋不上天呢
=华丽的分割线=====
向前跑
你咋不上天呢
=华丽的分割线=====
毁了你丫的

可以看到,MyMethod里的所有非static的方法都被拦截了,而static修饰的方法不会被拦截。

  • @within
    匹配所有持有指定注解的类。由于Spring AOP只是对AspectJ的一部分功能的模拟,所以@within和@target的效果几乎没有差别。但是在使用中推荐使用@target。这里不再展示代码。

  • @args
    匹配持有指定注解的参数类型的类。大栗子:

@Target({ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation {
}
/**
 * @Author: kuromaru
 * @Date: Created in 14:16 2019/4/4
 * @Description:
 * @modified:
 */
@MyAnnotation
public class MyMethod {

    @MyAnnotation
    public void run() {
        System.out.println("向前跑");
    }

    public void jump(@MyAnnotation String name) {
        System.out.println(name + ",向上跳");
    }

    public static void distory(@MyAnnotation String name) {
        System.out.println("毁了你丫的" + name);
    }

    public void fly(ArgsTypewa argsTypewa) {
        System.out.println(argsTypewa.getName() + "会飞");
    }

    public void sit(ArgsType argsType) {
        System.out.println(argsType.getName() + ", 坐下");
    }

    public void swim(@MyAnnotation ArgsType argsType) {
        System.out.println(argsType.getName() + "会游泳");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 14:19 2019/4/4
 * @Description:
 * @modified:
 */
@Aspect
public class MyExecution {

    @Pointcut("@args(MyAnnotation))")
    private void pointcut() {}

    @After("pointcut()")
    private void after() {
        System.out.println("你咋不上天呢");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 14:20 2019/4/4
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyMethod myMethod() {
        return new MyMethod();
    }

    @Bean
    public MyExecution myExecution() {
        return new MyExecution();
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:25 2019/4/4
 * @Description:
 * @modified:
 */
@MyAnnotation
public class ArgsTypewa {

    private String name;

    private String value;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:25 2019/4/4
 * @Description:
 * @modified:
 */
public class ArgsType {

    private String name;

    private String value;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 14:21 2019/4/4
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        MyMethod myMethod = acac.getBean(MyMethod.class);
        myMethod.jump("Jack");
        System.out.println("=======================华丽的分割线===========================");
        myMethod.run();
        System.out.println("=======================华丽的分割线===========================");
        MyMethod.distory("Mike");
        System.out.println("=======================华丽的分割线===========================");
        ArgsTypewa argsTypewa = new ArgsTypewa(){{
            setName("狗剩");
        }};
        myMethod.fly(argsTypewa);
        System.out.println("=======================华丽的分割线===========================");
        ArgsType argsType = new ArgsType(){{
            setName("铁蛋");
        }};
        myMethod.sit(argsType);
        System.out.println("=======================华丽的分割线===========================");
        myMethod.swim(argsType);
    }
}

输出结果:

四月 04, 2019 3:30:55 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 15:30:55 CST 2019]; root of context hierarchy
Jack,向上跳
=华丽的分割线=====
向前跑
=华丽的分割线=====
毁了你丫的Mike
=华丽的分割线=====
狗剩会飞
你咋不上天呢
=华丽的分割线=====
铁蛋, 坐下
=华丽的分割线=====
铁蛋会游泳

可以看到,在参数前面加注解是不生效的。

  • @annotation
    匹配所有含有指定注解的方法。代码与@args的代码相似,只要把@args改成@annotation,执行结果如下:

四月 04, 2019 3:55:22 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 15:55:22 CST 2019]; root of context hierarchy
Jack,向上跳
=华丽的分割线=====
向前跑
你咋不上天呢
=华丽的分割线=====
毁了你丫的Mike
=华丽的分割线=====
狗剩会飞
=华丽的分割线=====
铁蛋, 坐下
=华丽的分割线=====
铁蛋会游泳

可以看到,只有含有@MyAnnotation的方法被拦截了,其他的都没有。

  • bean
    匹配对象名字符合特定名字特征的类。如下
/**
 * @Author: kuromaru
 * @Date: Created in 13:53 2019/4/4
 * @Description:
 * @modified:
 */
public class ArgsType {

    private String name;

    private String value;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:54 2019/4/4
 * @Description:
 * @modified:
 */
public class MyMethod {

    public void showArgName(ArgsType argsType) {
        System.out.println(argsType.getName());
    }

    public void showArgName(String name) {
        ArgsType argsType = new ArgsType(){{
            setName(name);
        }};
        System.out.println(argsType.getName());
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:56 2019/4/4
 * @Description:
 * @modified:
 */
@Aspect
public class MyExecution {

    @Pointcut("bean(ArgsType)")
    private void myExecution() {

    }

    @Before("myExecution()")
    private void before(JoinPoint joinPoint) {
        System.out.println("马上打印" + joinPoint.getTarget().toString() + "的参数");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 15:59 2019/4/3
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean("ArgsType")
    public MyMethod myMethod() {
        return new MyMethod();
    }

    @Bean
    public MyExecution myExecution() {
        return new MyExecution();
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 16:00 2019/4/3
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        MyMethod myMethod = acac.getBean(MyMethod.class);
        ArgsType argsType = new ArgsType(){{
           setName("paramA");
           setValue("1");
        }};
        System.out.println("传入ArgsType");
        myMethod.showArgName(argsType);
        System.out.println("传入String");
        myMethod.showArgName("paramB");
    }
}

输出结果:

四月 04, 2019 4:03:12 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 16:03:12 CST 2019]; root of context hierarchy
传入ArgsType
马上打印configure.pointcut.bean.MyMethod@28701274的参数
paramA
传入String
马上打印configure.pointcut.bean.MyMethod@28701274的参数
paramB

而如果把Bean的名字改掉,就会无法拦截

/**
 * @Author: kuromaru
 * @Date: Created in 15:59 2019/4/3
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean("myMethod")
    public MyMethod myMethod() {
        return new MyMethod();
    }

    @Bean
    public MyExecution myExecution() {
        return new MyExecution();
    }
}

输出结果:

四月 04, 2019 4:06:39 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Thu Apr 04 16:06:39 CST 2019]; root of context hierarchy
传入ArgsType
paramA
传入String
paramB

  1. advice
    spring提供了五种advice,分别是:
  • Before:前置通知,在方法执行之前执行增强处理
  • AfterReturning:后置通知,在方法返回之后执行增强处理
  • AfterThrowing:后置通知,在方法抛出异常之后执行增强处理
  • After:后置通知,在方法退出之后执行增强处理,在finally之后
  • Around:环绕通知,在方法执行之前和之后执行增强处理

其中,AfterReturning和AfterThrowing可以获取到返回值和Exception对象,如下:

/**
 * @Author: kuromaru
 * @Date: Created in 10:25 2019/4/8
 * @Description:
 * @modified:
 */
public class MyMethod {

    public String testAfterReturn() {
        return "testAfterReturn将被AfterReturn拦截";
    }

    public void testAfterThrowing() throws Exception {
        throw new Exception("testAfterThrowing将被AfterThrowing拦截");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 10:31 2019/4/8
 * @Description:
 * @modified:
 */
@Aspect
public class MyAspect {

    @AfterReturning(pointcut = "execution(public String testAfterReturn(..))", returning = "retVal")
    private void afterReturning(String retVal) {
        System.out.println("打印返回值:");
        System.out.println(retVal);
    }

    @AfterThrowing(pointcut = "execution(public void testAfterThrowing(..))", throwing = "te")
    private void afterThrowing(Exception te) {
        System.out.println("打印异常信息:");
        System.out.println(te.getMessage());
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 10:40 2019/4/8
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyMethod myMethod() {
        return new MyMethod();
    }

    @Bean
    public MyAspect myAspect() {
        return new MyAspect();
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 10:41 2019/4/8
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        MyMethod myMethod = acac.getBean(MyMethod.class);

        myMethod.testAfterReturn();
        myMethod.testAfterThrowing();
    }
}

输出结果:

四月 08, 2019 10:43:13 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Mon Apr 08 10:43:13 CST 2019]; root of context hierarchy
打印返回值:
Exception in thread "main" java.lang.Exception: testAfterThrowing将被AfterThrowing拦截
testAfterReturn将被AfterReturn拦截
打印异常信息:
at configure.advice.MyMethod.testAfterThrowing(MyMethod.java:16)
testAfterThrowing将被AfterThrowing拦截
at configure.advice.MyMethod$$FastClassBySpringCGLIB$$df527efb.invoke()
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:736)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:671)
at configure.advice.MyMethod$$EnhancerBySpringCGLIB$$57307256.testAfterThrowing()
at configure.advice.Client.main(Client.java:18)

  1. AOP参数
  • JoinPoint:
/**
 * @Author: kuromaru
 * @Date: Created in 11:25 2019/4/8
 * @Description:
 * @modified:
 */
public class MyParam {

    private String name;
    private int age;
    private String interest;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getInterest() {
        return interest;
    }

    public void setInterest(String interest) {
        this.interest = interest;
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 11:25 2019/4/8
 * @Description:
 * @modified:
 */
public class MyMethod {

    public void printParam(MyParam myParam) {
        System.out.println(myParam.getName() + ": 今年" + myParam.getAge() + "岁,喜欢" + myParam.getInterest());
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:22 2019/4/8
 * @Description:
 * @modified:
 */
@Aspect
public class MyAspect {

    @Pointcut("execution(public void MyMethod.*(..))")
    private void pointcut() {
    }

    @Before("pointcut()")
    private void before(JoinPoint joinPoint) {
        System.out.println(joinPoint.getTarget().toString());
        System.out.println(joinPoint.getThis().toString());
        System.out.println(Arrays.toString(joinPoint.getArgs()));
        System.out.println(joinPoint.getKind());
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:25 2019/4/8
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyAspect myAspect() {
        return new MyAspect();
    }

    @Bean
    public MyMethod myMethod() {
        return new MyMethod();
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:26 2019/4/8
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        MyMethod myMethod = acac.getBean(MyMethod.class);
        MyParam myParam = new MyParam(){{
            setAge(20);
            setName("令狐冲");
            setInterest("吸星大法");
        }};

        myMethod.printParam(myParam);
    }
}

四月 08, 2019 1:48:56 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Mon Apr 08 13:48:56 CST 2019]; root of context hierarchy
configure.args.joinPoint.MyMethod@5b12b668
configure.args.joinPoint.MyMethod@5b12b668
[configure.args.joinPoint.Client$1@6150c3ec]
method-execution
令狐冲: 今年20岁,喜欢吸星大法

  • 参数名绑定
/**
 * @Author: kuromaru
 * @Date: Created in 13:22 2019/4/8
 * @Description:
 * @modified:
 */
@Aspect
public class MyAspect {

    @Pointcut("target(MyMethod) && args(myParam))")
    private void pointcut(MyParam myParam) {

    }

    @Before("pointcut(myParam)")
    private void before(MyParam myParam) {
        System.out.println(myParam.getName());
        System.out.println(myParam.getAge());
        System.out.println(myParam.getInterest());
    }
}

输出结果:

四月 08, 2019 2:04:43 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Mon Apr 08 14:04:43 CST 2019]; root of context hierarchy
令狐冲
20
吸星大法
令狐冲: 今年20岁,喜欢吸星大法

这种通过参数名绑定的方式来注入参数对象的形式,只适用于target、this和args,如果在其他的匹配类型中使用,则会抛出异常:

四月 08, 2019 2:06:46 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Mon Apr 08 14:06:46 CST 2019]; root of context hierarchy
四月 08, 2019 2:06:46 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.event.internalEventListenerProcessor': Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: error at ::0 name binding only allowed in target, this, and args pcds
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.event.internalEventListenerProcessor': Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: error at ::0 name binding only allowed in target, this, and args pcds
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.(AnnotationConfigApplicationContext.java:84)
at configure.args.arg.Client.main(Client.java:15)
Caused by: java.lang.IllegalArgumentException: error at ::0 name binding only allowed in target, this, and args pcds
at org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression(PointcutParser.java:319)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.buildPointcutExpression(AspectJExpressionPointcut.java:217)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.checkReadyToMatch(AspectJExpressionPointcut.java:190)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.getClassFilter(AspectJExpressionPointcut.java:169)
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:220)
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:279)
at org.springframework.aop.support.AopUtils.findAdvisorsThatCanApply(AopUtils.java:311)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply(AbstractAdvisorAutoProxyCreator.java:119)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:89)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:70)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:346)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:298)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:423)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1638)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
... 10 more

  • argNames绑定参数名
@Target({ElementType.PARAMETER, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
/**
 * @Author: kuromaru
 * @Date: Created in 11:25 2019/4/8
 * @Description:
 * @modified:
 */
public class MyMethod {

    @MyAnnotation
    public void printParam(MyParam myParam) {
        System.out.println(myParam.getName() + ": 今年" + myParam.getAge() + "岁,喜欢" + myParam.getInterest());
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:22 2019/4/8
 * @Description:
 * @modified:
 */
@Aspect
public class MyAspect {

    @Pointcut(value = "execution(public void MyMethod.*(..)) && args(myParam) && @annotation(theMethod)", argNames = "myParam, theMethod")
    private void pointcut(MyParam myParam, MyAnnotation theMethod) {

    }

    @Before("pointcut(myParam, theMethod)")
    private void before(MyParam myParam, MyAnnotation theMethod) {
        System.out.println(myParam.getName());
        System.out.println(myParam.getAge());
        System.out.println(myParam.getInterest());
        System.out.println(theMethod.annotationType());
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:25 2019/4/8
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyAspect myAspect() {
        return new MyAspect();
    }

    @Bean
    public MyMethod myMethod() {
        return new MyMethod();
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:26 2019/4/8
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        MyMethod myMethod = acac.getBean(MyMethod.class);
        MyParam myParam = new MyParam(){{
            setAge(20);
            setName("令狐冲");
            setInterest("吸星大法");
        }};

        myMethod.printParam(myParam);
    }
}

输出结果:

四月 08, 2019 2:24:00 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Mon Apr 08 14:24:00 CST 2019]; root of context hierarchy
令狐冲
20
吸星大法
interface configure.args.argNames.MyAnnotation
令狐冲: 今年20岁,喜欢吸星大法

  • AOP排序
    可以通过实现Ordered接口来达到可排序的效果。如下:
/**
 * @Author: kuromaru
 * @Date: Created in 13:22 2019/4/8
 * @Description:
 * @modified:
 */
@Aspect
public class MyAspect implements Ordered{

    @Pointcut(value = "execution(public void MyMethod.*(..)) && args(myParam) && @annotation(theMethod)", argNames = "myParam, theMethod")
    private void pointcut(MyParam myParam, MyAnnotation theMethod) {

    }

    @Before("pointcut(myParam, theMethod)")
    private void before(MyParam myParam, MyAnnotation theMethod) {
        System.out.println(myParam.getName());
        System.out.println(myParam.getAge());
        System.out.println(myParam.getInterest());
        System.out.println(theMethod.annotationType());
    }

    @Override
    public int getOrder() {
        return -1;
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:22 2019/4/8
 * @Description:
 * @modified:
 */
@Aspect
public class MyAspect2 implements Ordered{

    @Pointcut(value = "execution(public void MyMethod.*(..)) && args(myParam) && @annotation(theMethod)", argNames = "myParam, theMethod")
    private void pointcut(MyParam myParam, MyAnnotation theMethod) {

    }

    @Before("pointcut(myParam, theMethod)")
    private void before(MyParam myParam, MyAnnotation theMethod) {
        System.out.println("======================================");
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 13:25 2019/4/8
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean
    public MyAspect myAspect() {
        return new MyAspect();
    }

    @Bean
    public MyAspect2 myAspect2() {
        return new MyAspect2();
    }

    @Bean
    public MyMethod myMethod() {
        return new MyMethod();
    }
}

输出结果:

四月 08, 2019 2:29:16 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Mon Apr 08 14:29:16 CST 2019]; root of context hierarchy
令狐冲
20
吸星大法
interface configure.args.argNames.MyAnnotation
======================================
令狐冲: 今年20岁,喜欢吸星大法

可以看到,getOrder()方法返回的数值较小的会优先执行。

  • introduction
    Introduction可以为一个类提供额外的处理能力,比如为A类提供B类的方法。如下:
/**
 * @author kuromaru
 */
public interface Singer {

    void sing();
}
/**
 * @Author: kuromaru
 * @Date: Created in 11:04 2019/4/3
 * @Description:
 * @modified:
 */
public interface Performance {

    void jump();
}
/**
 * @Author: kuromaru
 * @Date: Created in 14:31 2019/4/3
 * @Description:
 * @modified:
 */
public class Dancer implements Performance {
    public void jump() {
        System.out.println("我能跳你脸上!");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 11:06 2019/4/3
 * @Description:
 * @modified:
 */
public class GreatSinger implements Singer {
    public void sing() {
        System.out.println("我会唱忐忑,@#¥%%");
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 10:40 2019/4/3
 * @Description:
 * @modified:
 */
@Aspect
public class SingIntroduction {

    @DeclareParents(value = "configure.introduction.Performance+", defaultImpl = GreatSinger.class)
    public static Singer singer;

    @Before("this(Singer+)")
    private void before() {
        System.out.println("我是歌者");
    }

    @Before("this(Performance+)")
    private void before2() {
        System.out.println("我是表演艺术家");
    }

}
/**
 * @Author: kuromaru
 * @Date: Created in 11:06 2019/4/3
 * @Description:
 * @modified:
 */
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {

    @Bean(name = "dancer")
    public Performance dancer() {
        return new Dancer();
    }

    @Bean
    public SingIntroduction singIntroduction() {
        return new SingIntroduction();
    }
}
/**
 * @Author: kuromaru
 * @Date: Created in 14:33 2019/4/3
 * @Description:
 * @modified:
 */
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyConfig.class);
        Performance dancer = (Performance) acac.getBean("dancer");
        Singer singer = (Singer) dancer;
        singer.sing();
        System.out.println("=====================================");
        dancer.jump();
    }
}

输出结果:

四月 08, 2019 2:44:27 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5387f9e0: startup date [Mon Apr 08 14:44:27 CST 2019]; root of context hierarchy
我会唱忐忑,@#¥%%
=====================================
我是歌者
我是表演艺术家
我能跳你脸上!

可以看到,dancer直接强转成了singer,并且可以正常调用相关方法。而dancer在执行自己的方法时,同时触发了两个before操作。根据this的语义,可以知道,dancer的代理类既代理了dancer,也代理了singer。

posted @ 2020-05-22 08:42  kuromaru  阅读(268)  评论(0)    收藏  举报