Spring底层分析--9.切点匹配和切面实现

本文详细介绍了SpringAOP中的切点查找方法,包括通过execution表达式、类和方法查找以及接口上的注解。此外,还探讨了Aspect和Advisor的概念、创建实例以及代理的创建时机和执行顺序。

目录

 

一.切点查找

二.示例

1.通过execution表达式来找切点

2.在类和类的方法上查找切点

3.接口上查找@Transactional注解

三.Aspect和Advisor

1.概念

2.示例

(1)创建A1类

(2)创建切面类MyAspect

(3)创建配置类,加入低级切面Advisor

(4)创建容器,注册bean处理器

四.两个重要方法

1.findEligibleAdvisors

2.wrapIfNecessary

五.代理创建时机

六.切面执行顺序


 

一.切点查找

我们只有找到切点了,才能去添加增强逻辑,那么我们如何找到切点呢?

二.示例

1.通过execution表达式来找切点

创建F1类

public class F1 {


    public void foo(){
        System.out.println("foo...");
    }

    @Bean
    public void bar(){
        System.out.println("bar...");
    }
}

我们需要找到foo()方法和被@Bean注解的地方,这两个作为切点,下面我们来实现:

class Test{
    public static void main(String[] args) throws NoSuchMethodException {

        F1 f1 = new F1();

        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        boolean b = pointcut.matches(f1.getClass().getDeclaredMethod("foo"), f1.getClass());
        System.out.println(b);

        AspectJExpressionPointcut pointcut1 = new AspectJExpressionPointcut();
        pointcut1.setExpression("@annotation(org.springframework.context.annotation.Bean)");
        boolean b1 = pointcut.matches(f1.getClass().getDeclaredMethod("foo"), f1.getClass());
        System.out.println(b1);


    }
}

通过matches()方法可以匹配切点,看他是否含有切点表达式中的的方法或注解

运行一下:

true
true

进程已结束,退出代码0

如果有,就返回true

2.在类和类的方法上查找切点

下面我们来判断类上和类的方法上是否含有@Transactional注解

我们创建F2和F3类

@Transactional
class F2{
    public void foo(){
        System.out.println("foo...");
    }
    public void bar(){
        System.out.println("bar...");
    }
}

class F3{

    @Transactional
    public void foo3(){
        System.out.println("foo3...");
    }

}

下面我们来匹配切点:

 StaticMethodMatcher pointcut2 = new StaticMethodMatcher() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                //获取method上的注解信息
                MergedAnnotations annotations = MergedAnnotations.from(method);

                //如果方法上含有@Transactional
                if(annotations.isPresent(Transactional.class)){
                    return true;
                }
                //如果类上含有@Transactional
                MergedAnnotations annotations1 = MergedAnnotations.from(targetClass);
                if(annotations1.isPresent(Transactional.class)){
                    return true;
                }
                return false;
            }
        };

        System.out.println(pointcut2.matches(F2.class.getMethod("foo"), F2.class));

        System.out.println(pointcut2.matches(F3.class.getMethod("foo3"), F3.class));

这里使用的是StaticMethodMatcher类中的matches方法来匹配,它接受两个参数:

1.哪个方法,2.目标类的类型

然后使用了MergedAnnotations 的from方法,该方法返回方法或者类上的注解信息,然后就可以判断了

下面运行一下:


true
true

进程已结束,退出代码0

3.接口上查找@Transactional注解

@Transactional
interface I4{
    void cool();
}
class F4 implements I4{

    @Override
    public void cool() {
        System.out.println("cool...");
    }
}

我们可以更改他的查找策略

   MergedAnnotations annotations1 = MergedAnnotations.from(targetClass,MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);

这样就可以成功的找到接口上的注解了

三.Aspect和Advisor

1.概念

Aspect是高级切面,它可以定义多个切点和通知

Advisor是低级切面,它只能定义一个切点和通知

Aspect底层会转化为每个Advisor

2.示例

(1)创建A1类

class A1 {

    public void go() {
        System.out.println("go...");
    }

}

(2)创建切面类MyAspect

@Aspect
public class MyAspect {
    @Before("execution(* go())")
    public void before(){
        System.out.println("before...");
    }
    @After("execution(* go())")
    public void after(){
        System.out.println("after...");
    }


}

(3)创建配置类,加入低级切面Advisor

@Configuration
public
class MyConfig {
    @Bean
    public Advisor advisor(MethodInterceptor advice) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* go())");
        //添加切点和通知
        return new DefaultPointcutAdvisor(pointcut, advice);
    }

    @Bean
    public MethodInterceptor advice() {
        return invocation -> {
            System.out.println("advisor before...");
            Object result = invocation.proceed();
            System.out.println("advisor after...");
            return result;
        };
    }

}

这里我们把advisor需要的通知MethodInterceptor作为一个Bean,可以自动注入

(4)创建容器,注册beanFactory处理器

 public static void main(String[] args) {

        GenericApplicationContext context = new GenericApplicationContext();

        //注册config
        context.registerBean("myConfig", MyConfig.class);
        //注册aspect
        context.registerBean("myAspect", MyAspect.class);
        //注册bean后处理器解析@Bean注解
        context.registerBean(ConfigurationClassPostProcessor.class);
       

        context.refresh();


        //获取这些bean
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

}

先运行一下,查看有哪些bean

myConfig
myAspect
org.springframework.context.annotation.ConfigurationClassPostProcessor
advisor
advice

下面我们需要获取所有的切面:

我们需要使用AnnotationAwareAspectJAutoProxyCreator

查看他的类图:

8da98b53bbc448369473a3cce6920896.png

 我们选取它的父类AbstractAdvisorAutoProxyCreator中的

findEligibleAdvisors方法,因为它是protected修饰的,所以我们不能直接调用它,这里我们可以把我们的测试类的包名修改为它的包名:
package org.springframework.aop.framework.autoproxy;

这样我们就可以使用findEligibleAdvisors方法了,它是判断哪个类是否有资格获取切面,

我们使用一下它:

在这之前先要把它注册到容器中去

 //注册解析aspect的后处理器
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
  AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        //查找谁有资格被切点匹配
        List<Advisor> advisors = creator
                .findEligibleAdvisors(org.springframework.aop.framework.autoproxy.A1.class, "A1");

        advisors.forEach(System.out::println);

 然后我们运行一下,查看有哪些切面:

org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* go())]; advice [com.jjh.aspect.MyConfig$$Lambda$89/0x00000008001b2440@a4add54]
InstantiationModelAwarePointcutAdvisor: expression [execution(* go())]; advice method [public void com.jjh.aspect.MyAspect.before()]; perClauseKind=SINGLETON
InstantiationModelAwarePointcutAdvisor: expression [execution(* go())]; advice method [public void com.jjh.aspect.MyAspect.after()]; perClauseKind=SINGLETON

我们发现这里出现了四个切面,解释一下:

第一个切面是Spring为每一个代理都添加的切面,是默认的

第二个切面是我们通过配置类中添加的Advisor

第三和第四个是@Aspect注解中的@Before和@After创建的两个低级切面,@Aspect这个MyAspect是一个高级切面,它底层创建了两个Advisor低级切面

四.两个重要方法

1.findEligibleAdvisors

这个就是上面那个方法,它主要是对目标类进行 匹配,查找是否有切面是针对它的,如果有,就返回切面的集合,没有返回空

2.wrapIfNecessary

该方法是对上面的切面就行判断,如果这个切面集合不为空,就对这个目标类创建代理对象,如果集合为空,就返回这个对象本身

3.下面我们来验证一下:

  /**
         *   wrapIfNecessary方法对上面找的的切面集合做一个判断
         *   如果有切面,就给这个目标对象创建代理
         *   如果没有切面,就返回这个对象本身
         */


        Object o = creator
                .wrapIfNecessary(new org.springframework.aop.framework.autoproxy.A1(), "a1", "a1");

        System.out.println(o.getClass());

我们运行一下,查看它返回对象的类型:

class org.springframework.aop.framework.autoproxy.A1$$EnhancerBySpringCGLIB$$a7ae3c69

我们看到是cglib代理的,说明我们的假设成功

下面我们调用这个代理对象的go()方法:

 //对这个代理对象进行类型转换为A1,调用方法,查看是否增强
        org.springframework.aop.framework.autoproxy.A1 o1 =
                (org.springframework.aop.framework.autoproxy.A1) o;

        o1.go();

运行查看:

advisor before...
before...
go...
after...
advisor after...

进程已结束,退出代码0

可以看到已经实现了增强功能,说明了aop的底层就是通过代理来实现的!

五.代理创建时机

代理创建顺序为以下:

创建 -> (*) ->依赖注入->初始化->(*)

分为两种情况:

1.当有循环依赖时,代理对象将在依赖注入之前被创建

2.单向注入时,代理对象将在初始化之后被创建

六.切面执行顺序

高级切面可以使用@Order()来设置,值越小,越前执行

低级切面可以通过setOrder()方法来设置优先级

七.高级切面向低级切面转换

我们来模拟一下高级切面转化为低级切面

@Aspect
public class MyAspect {
    @Before("execution(* go())")
    public void before(){
        System.out.println("before...");
    }
    @After("execution(* go())")
    public void after(){
        System.out.println("after...");
    }


}

我们先把这个@Before注解的方法创建一个Before低级切面


        //高级切面向低级切面转换
        MyAspect myAspect = context.getBean(MyAspect.class);

        Method method = null;
        try {
            method = myAspect.getClass().getDeclaredMethod("before");
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

        AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
        //创建切面集合
        List<Advisor> list = new ArrayList<>();
        //判断method上是否有@Before注解
        if (method.isAnnotationPresent(Before.class)) {
            //如果有,获取注解中的切点表达式值
            String expession = method.getAnnotation(Before.class).value();
            //创建切点
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(expession);
            //创建一个通知
            AspectJMethodBeforeAdvice advice =
                    new AspectJMethodBeforeAdvice(method,pointcut,factory);
            //创建一个低级切面
            Advisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
            //添加入集合中
            list.add(advisor);
        }

        //遍历集合
        for (Advisor advisor : list) {
            System.out.println(advisor);
        }

    }

运行一下:

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* go())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void com.jjh.aspect.MyAspect.before()]; aspect name '']

发现成功的将before切面创建出来了

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值