SpringAOP源码解析DayOne

SpringAOP源码解析DayOne

  • 前言
    • AOP 术语解释
    • 通过例子看注解式的AOP
    • SpringAop演变过程
    • 责任链模式
    • spring aop源码解析一

前言

  • AOP 要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。
  • AOP 的实现并不是因为 Java 提供了什么神奇的钩子,可以把方法的几个生命周期告诉我们,而是我们要实现 一个代理,实际运行的实例其实是生成的代理类的实例。

AOP 术语解释

  • 切面是用来管理通知和切点的
  • 增强的代码都写到通知里。
  • 通过切点来指定需要被增强的方法
  • 把通知增强到我们的方法里,这个过程叫织入,SpringAop使用动态代理实现
  • 连接点代表着被增强的方法,比如crud方法
  • 目标对象就是需要被增强的对象
  • Advisor(顾问)封装了Advice和Pointcut

它基于动态代理来实现。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现。

通过例子看注解式的AOP

@EnableAspectJAutoProxy 等同于aop:aspectj-autoproxy/
SpringAOP源码解析DayOne_第1张图片

/**
 * 计算类接口
 */
public interface Calculate {

    /**
     * 加法
     * @param numA
     * @param numB
     * @return
     */
     int add(int numA, int numB);

    /**
     * 减法
     * @param numA
     * @param numB
     * @return
     */
     int sub(int numA, int numB);

    /**
     * 除法
     * @param numA
     * @param numB
     * @return
     */
     int div(int numA, int numB);

    /**
     * 乘法
     * @param numA
     * @param numB
     * @return
     */
     int multi(int numA, int numB);

    int mod(int numA, int numB);
}
@Component
public class TulingCalculate implements Calculate {

    public int add(int numA, int numB) {

        System.out.println("执行目标方法:add");
        return numA+numB;
    }

    public int sub(int numA, int numB) {
        System.out.println("执行目标方法:reduce");
        return numA-numB;
    }

    public int div(int numA, int numB) {
        System.out.println("执行目标方法:div");
        return numA/numB;
    }

    public int multi(int numA, int numB) {
        System.out.println("执行目标方法:multi");

        return numA*numB;
    }

    public int mod(int numA,int numB){
        System.out.println("执行目标方法:mod");

		int retVal = ((Calculate)AopContext.currentProxy()).add(numA,numB);
       
        return retVal%numA;
    }

}
@Aspect
@Order
@Component
public class TulingLogAspect {

    @Pointcut("execution(* tuling.TulingCalculate.*(..))")
    public void pointCut(){};

    @Before(value = "pointCut()")
    public void methodBefore(JoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<前置通知>,入参"+ Arrays.asList(joinPoint.getArgs()));
    }

    @After(value = "pointCut()")
    public void methodAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<后置通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }

    @AfterReturning(value = "pointCut()",returning = "result")
    public void methodReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<返回通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }

    @AfterThrowing(value = "pointCut()")
    public void methodAfterThrowing(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<异常通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }

}
@Configuration
@EnableAspectJAutoProxy   /**/
@ComponentScan("tuling")
public class MainConfig {

     @Bean
    public Calculate calculate() {
        return new TulingCalculate();
    }

    @Bean
    public TulingLogAspect tulingLogAspect() {
        return new TulingLogAspect();
    }


    @Bean
    public Calculate calculate2() {
        return new TulingCalculate();
    }
}
public static void main(String[] args) {

    	AnnotationConfigApplicationContext ctx = 
    	      new AnnotationConfigApplicationContext(MainConfig.class);

        Calculate calculate = (Calculate) ctx.getBean("tulingCalculate");
        int retVal = calculate.div(2,4);
}

SpringAOP源码解析DayOne_第2张图片

SpringAop演变过程

目前 Spring AOP 一共有三种配置方式,Spring 做到了很好地向下兼容,所以大家可以放心使用。

  • Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的,想看源码的可以从这里起步。
  • Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间
  • Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫 做
    @AspectJ,但是这个和 AspectJ 其实没啥关系

我们看下最早的时候没有切面切点那些概念的时候,如何用接口的形式实现
举个例子

Spring常见代理创建方式

 FactoryBean方式,创建单个动态代理: ProxyFactoryBean 
  1.指定续增强的接口:proxyInterfaces 
  2.指定需增强的实现类target   
  3.指定Advice、Advisor、Interceptor都行
public class EalyAopMainConfig {

    // 被代理对象
    @Bean
    public Calculate tulingCalculate() {
        return new TulingCalculate();
    }

    // Advice 方式
    @Bean
    public TulingLogAdvice tulingLogAdvice(){
        return new TulingLogAdvice();
    }

    // Interceptor方式 , 可以理解为环绕通知
    @Bean
    public TulingLogInterceptor tulingLogInterceptor() {
        return new TulingLogInterceptor();
    }


    /**
     * FactoryBean方式单个: ProxyFactoryBean
     *
     * 此中方法有个致命的问题,如果我们只能指定单一的Bean的AOP,
     * 如果多个Bean需要创建多个ProxyFactoryBean 。
     * 而且,我们看到,我们的拦截器的粒度只控制到了类级别,类中所有的方法都进行了拦截。
     * 接下来,我们看看怎么样只拦截特定的方法。
     * @return*/
    @Bean
     public ProxyFactoryBean calculateProxy(){
         ProxyFactoryBean userService=new ProxyFactoryBean();
   userService.setInterceptorNames("tulingLogAdvice","tulingLogInterceptor");          
         // 根据指定的顺序执行
         userService.setTarget(tulingCalculate());
         return userService;
     }
}
public static void main(String[] args) {
 AnnotationConfigApplicationContext ctx = 
     new AnnotationConfigApplicationContext(EalyAopMainConfig.class);
 Calculate calculateProxy = ctx.getBean("calculateProxy",Calculate.class);
        System.out.println(calculateProxy.getClass());
        calculateProxy.div(1,1);
}

创建的是tulingCalculate的动态代理,所以可以用接口来接收。
SpringAOP源码解析DayOne_第3张图片

ProxyFactoryBean实现原理:把这些通知放到链当中,然后利用责任链模式调用动态代理。

责任链模式

如果要用责任链,

  1. 要保证整个链上的都要用同一个抽象,这样才能统一调用。
  2. 要么用循环,要么用递归去实现。
    给大家看个例子:
    定义一个统一的前置拦截:
public class MethodBeforeAdviceInterceptor implements MethodInterceptor {

    // 前置通知
    MethodBeforeAdvice methodBeforeAdvice;

    public MethodBeforeAdviceInterceptor(
                    MethodBeforeAdvice methodBeforeAdvice) {
        this.methodBeforeAdvice = methodBeforeAdvice;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

       methodBeforeAdvice.before(
           invocation.getMethod(),
           invocation.getArguments(),invocation.getThis()
       );
       return invocation.proceed();
    }
}

之前的TulingLogAdvice也是继承了MethodBeforeAdvice

public class TulingLogAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        String methodName = method.getName();
        System.out.println("执行目标方法【" + methodName + "】的<前置通知>,入参" + Arrays.asList(args));
    }
}

那么看下main

public class MainStart {

    public static void main(String[] args) throws Throwable {
        // 把一条链上的都初始化
        List<MethodInterceptor> list=new ArrayList<>();
        list.add(new MethodBeforeAdviceInterceptor(new TulingLogAdvice()));
        list.add(new TulingLogInterceptor());

        // 递归依次调用
        MyMethodInvocation invocation=new MyMethodInvocation(list);
        invocation.proceed();
    }

    public static class MyMethodInvocation implements MethodInvocation{
        protected List<MethodInterceptor> list;
        protected final TulingCalculate target;

        public MyMethodInvocation(List<MethodInterceptor> list) {
            this.list = list;
            this.target = new TulingCalculate();
        }
        int i=0;
        @Override
        public Object proceed() throws Throwable {
            if(i==list.size()){
                return target.add(2,2);
            }
            MethodInterceptor mi = list.get(i);
            i++;
           return  mi.invoke(this);
        }

        @Override
        public Object getThis() {
            return target;
        }

        @Override
        public AccessibleObject getStaticPart() {
            return null;
        }


        @Override
        public Method getMethod() {
            try {

                return target.getClass().getMethod("add",int.class,int.class);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        public Object[] getArguments() {
            return new Object[0];
        }
    }

}

运行结果:
SpringAOP源码解析DayOne_第4张图片

刚刚介绍的ProxyFactoryBean这种方式很麻烦,一个被增强的类就需要定义一个ProxyFactoryBean。如果有很多就会有很多ProxyFactoryBean。
并且只能控制到类的级别,方法的就不能控制了。

接下哎我们看看怎么拦截特定方法:

 /**
     * Advisor 种类很多:
     * RegexpMethodPointcutAdvisor 按正则匹配类
     * NameMatchMethodPointcutAdvisor 按方法名匹配
     * DefaultBeanFactoryPointcutAdvisor xml解析的Advisor   
    @Bean
    public NameMatchMethodPointcutAdvisor tulingLogAspectAdvisor() {
        NameMatchMethodPointcutAdvisor advisor=new NameMatchMethodPointcutAdvisor();
        // 通知(Advice)  :是我们的通知类 没有带切点
        // 通知者(Advisor):是经过包装后的细粒度控制方式。 带了切点
        advisor.setAdvice(tulingLogAdvice());
        advisor.setMappedNames("div");
        return  advisor;
    }
	@Bean
    public ProxyFactoryBean calculateProxy(){
        ProxyFactoryBean userService=new ProxyFactoryBean();
        userService.setInterceptorNames("tulingLogAspectAdvisor");
        userService.setTarget(tulingCalculate());
        return userService;
    }

注解方式的AOP会把@Before @After等注册成一个Advisor

这种方式还是没有解决每一个增强的类配置一个ProxyFactoryBean
	@Bean
     public BeanNameAutoProxyCreator autoProxyCreator() {
     BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
     //设置要创建代理的那些Bean的名字
     beanNameAutoProxyCreator.setBeanNames("tuling*");
     //设置拦截链名字(这些拦截器是有先后顺序的)
     beanNameAutoProxyCreator.setInterceptorNames("tulingLogAspectAdvisor");
     return beanNameAutoProxyCreator;
     }
 public static void main(String[] args) {

        AnnotationConfigApplicationContext ctx = 
        		new AnnotationConfigApplicationContext(EalyAopMainConfig.class);
        Calculate tulingCalculate = ctx.getBean("tulingCalculate",Calculate.class);
        tulingCalculate.div(1,1);
}

接下来看下看下BeanNameAutoProxyCreator的方式

/**
     *     autoProxy: BeanPostProcessor手动指定Advice方式  BeanNameAutoProxyCreator
     * @return */
     @Bean
     public BeanNameAutoProxyCreator autoProxyCreator() {
     BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
     //设置要创建代理的那些Bean的名字
     beanNameAutoProxyCreator.setBeanNames("tuling*");
     //设置拦截链名字(这些拦截器是有先后顺序的)
     beanNameAutoProxyCreator.setInterceptorNames("tulingLogAdvice");
     return beanNameAutoProxyCreator;
     }
 public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = 
            new AnnotationConfigApplicationContext(EalyAopMainConfig.class);
        Calculate tulingCalculate = ctx.getBean("tulingCalculate",Calculate.class);
        tulingCalculate.div(1,1);
    }

可以看到这样距离Aspect的方式已经很近了

spring aop源码解析一

我们知道,spring中的aop是通过动态代理实现的,那么他具体是如何实现的呢?spring通过一个切面类,在他的类上加入@Aspect注 解,定义一个Pointcut方法,最后定义一系列的增强方法。这样就完成一个对象的切面操作。
那么思考一下,按照上述的基础,要实现我们的aop,大致有以下思路:

  1. 找到所有的切面类
  2. 解析出所有的advice并保存
  3. 创建一个动态代理类
  4. 调用被代理类的方法时,找到他的所有增强器,并增强当前的方法

先来看是怎么解析切面的
是通过AnnotationAwareAspectJAutoProxyCreator这个bean的后置处理器生效的。看下类结构
SpringAOP源码解析DayOne_第5张图片
会在第一个bean的后置处理器的地方去解析切面
SpringAOP源码解析DayOne_第6张图片
AbstractAutoProxyCreator类的顶层InstantiationAwareBeanPostProcessor,调用
postProcessBeforeInstantiation
在这里插入图片描述
子类实现,AbstractAutoProxyCreator

SpringAOP源码解析DayOne_第7张图片
会在第一个Bean创建的时候,调用Bean的后置处理器,解析所有的切面。这个动作是非常消耗性能的,所以会放到缓存中。

@Override
	public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		//构建我们的缓存key
		Object cacheKey = getCacheKey(beanClass, beanName);
		// 没有beanName 或者  没有包含在targetSourcedBeans中(一般都不会包含,因为targetSource需要手动设置,一般情况不会设置)
		if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
			//被解析过 直接返回
			if (this.advisedBeans.containsKey(cacheKey)) {
				return null;
			}
			/**
			 * 注意看重写方法
			 * 判断是不是基础的bean (是不是切面类、通知、切点等)
			 * 判断是不是应该跳过 默认false (切面解析也在其中)
			 */
			if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
				this.advisedBeans.put(cacheKey, Boolean.FALSE);
				return null;
			}
		}

		/**
		 * TargetSource   代理逻辑的实现,,    在创建代理时默认是SingletonTargetSource
		 * 所以如果指定了TargetSource 说明有自己的代理逻辑实现,在这就直接创建代理
		 */
		TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
		if (targetSource != null) {
			if (StringUtils.hasLength(beanName)) {
				this.targetSourcedBeans.add(beanName);
			}
			Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
			Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		return null;
	}

是不是跳过:

@Override
	protected boolean shouldSkip(Class<?> beanClass, String beanName) {
		/**
		 * 找到候选的Advisors(通知  前置通知、后置通知等..)
		 */
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		for (Advisor advisor : candidateAdvisors) {
			// 判断这个类的原因在于:
			// AspectJPointcutAdvisor 是xml 
			// 如果   是当前beanName 就说明当前bean是切面类  那就跳过。
			if (advisor instanceof AspectJPointcutAdvisor &&
					((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
				return true;
			}
		}
		return super.shouldSkip(beanClass, beanName);
	}

找到候选的Advisors(通知 前置通知、后置通知等…)

protected List<Advisor> findCandidateAdvisors() {
		// 找出xml配置的Advisor和原生接口的AOP的Advisor   找出事务相关的advisor
		List<Advisor> advisors = super.findCandidateAdvisors();
		//找出Aspect相关的信息之后封装为一个advisor
		if (this.aspectJAdvisorsBuilder != null) {
			advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
		}
		//返回我们所有的通知
		return advisors;
	}

去容器中获取到所有的切面信息保存到缓存中buildAspectJAdvisors。得到切面类的各种增强方法放到advisors中。未完待续。。。

你可能感兴趣的:(spring源码,java,aop,spring)