Spring-AOP

AOP 专题(注解版本)

代码库:

https://gitee.com/chenscript/spring_ioc_aop_mvc_learning.git

1、AOP是什么?

AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统
OOP(Object-Oriented Programming, 面向对象编程) 的补充. AOP 的主要编程对象是切面(aspect),
而切面模块化横切关注点.

2、AOP解决什么问题?

  1. 所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,
    便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
  2. 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
    业务模块更简洁了,只包含核心的业务代码

3、AOP如何实现?

  1. main()
public class App {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigAOP.class);
		MathCalculator mathCalculator = (MathCalculator) context.getBean("mathCalculator");
		int div = mathCalculator.div(1, 2);
	}
}
  1. 配置类
@EnableAspectJAutoProxy
@Configuration
public class MainConfigAOP {
	@Bean
	public MathCalculator mathCalculator(){
		return new MathCalculator();
	}
	@Bean
	public LogAspects logAspects(){
		return new LogAspects();
	}
}

  1. 切点类
public class MathCalculator {
		public int div(int i,int j){
			System.out.println("div...");
			return i/j;
		}
}
  1. 切面 @Pointcut()定义切点位置,后面是五个aop的切面注解,

@Before: 方法运行前执行
@After: 方法运行后执行
@AfterReturning: 方法返回结果后执行
@AfterThrowing: 异常时执行
@Around: 围绕方法,可以在方法前后添加操作

@Aspect
public class LogAspects {
	@Pointcut("execution(public int com.spring.springaop.test01.MathCalculator.*(..))")
	public void pointCut(){};
	
	@Before("pointCut()")
	public void logStart(JoinPoint joinPoint){
		System.out.println(joinPoint.getSignature().getName()+"除法运行...参数是{}");
	}
	@After("pointCut(){}")
	public void logEnd(){
		System.out.println("除法结束");
	}
	@AfterReturning(value = "pointCut()",returning = "result")
	public void logReturn(Object result){
		System.out.println("除法正常返回。。。运行结果:{}"+result);
	}
	@AfterThrowing(value = "pointCut()",throwing = "ex")
	public void logException(JoinPoint joinPoint,Exception ex){
		System.out.println(joinPoint.getSignature().getName()+"异常:{}"+ex.getMessage());
	}
}

运行结果:

div除法运行…参数是{}
div…
除法结束
除法正常返回。。。运行结果:{}0

4、AOP原理是什么? (动态代理+反射)

从main()函数开始,我们跟踪代码进去看看。
当打断点看的时候发现,容器会增加一个自动代理对象的初始化过程。
在这里插入图片描述
相比ioc流程中的常规bean的初始化,可以推理得出org.springframework.aop.config.internalAutoProxyCreator代理对象应该就是这个类在作怪了。怎么验证呢?
其实我们可以通过注解@EnableAspectJAutoProxy知道。

1.探索@EnableAspectJAutoProxy

点开注解,可以看到段代码:

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

引入了AspectJAutoProxyRegistrar类,进行代理解析。 在这里,又能假设了之前说的internalAutoProxyCreator类可能就是这个代理注册类或者是由它生成的。
继续看代码~
点进AspectJAutoProxyRegistrar类中,

操作一:注册切面注解创建器

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

操作二: 暴露注册器出来

AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);

看来主要操作还是在操作一。再点进去看看

registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);

@Nullable
	public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
	}

从字面上看,这儿是要创建一个注解代理器,然后注册到容器。所以需要观察AnnotationAwareAspectJAutoProxyCreator.class的主要逻辑

点进这个类,好像没什么思路,对吧。没错,这时候就该想想,是不是被容器触发器搞上了? 怎么才能在容器启动的时候被搞上呢,无非就是ioc里面的xxxAware,xxxBeanPostProcessor。所以,要被搞上,就要去继承这两种类型的类呗。好的,去看看继承关系。
用IDEA,ctrl+H 可以看到,继承了AbstractAutoProxyCreator,实现XXBeanPostProcessor和BeanFactoryAware接口。 也就是说,会在ioc中的bean后置处理器执行阶段搞点事,以及会做一些关于beanFactory的事。
在这里插入图片描述
仔细看了一下AnnotationAwareAspectJAutoProxyCreator类的方法,确实有一个关于工厂的操作,而且除了初始化默认的工厂之外,还多构建了工厂和适配器:ReflectiveAspectJAdvisorFactoryBeanFactoryAspectJAdvisorsBuilderAdapter。 这么特殊的做处理,那肯定是跟它们有关了。
ReflectiveAspectJAdvisorFactory:for bean pointcut handling
BeanFactoryAspectJAdvisorsBuilderAdapter:就是个托。BeanFactoryAspectJAdvisorsBuilderAdapter的子类,它委托给周围的AnnotationAwareAspectJAutoProxyCreator设施

	@Override
	protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		super.initBeanFactory(beanFactory);
		if (this.aspectJAdvisorFactory == null) {
			this.aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(beanFactory);
		}
		this.aspectJAdvisorsBuilder =
				new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory);
	}

这一步最终目的还只是给容器配上一个AspectJAdvisorFactory单例而已,也就是this.aspectJAdvisorFactory = new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory);生成的工厂。
到这里初始化部分就结束了,开始aop调用。

2.探索aop动态代理调用过程。

将断点打在这,然后进入方法调试(F7)
在这里插入图片描述

就进入到了拦截方法,

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable

主要步骤:

  • List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
  • retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
  • retVal = processReturnType(proxy, target, method, retVal);

看到上面的主要步骤,一下子蒙蔽了,为什么马上就是Cglib代理了? 不是还有个jdk动态代理吗? 判断在哪?
于是我找到了答案:
先看调用链深度:
在这里插入图片描述
然后是代码:
判断创建的是哪个aop代理:

@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

也就是说,在初始化的时候就已经定是哪个动态代理类型了。这个例子中,我没有设置,就默认给我取了cglib动态代理对象进行调用。

好的,现在转回到intercept的主要流程:

  • List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
  • retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
  • retVal = processReturnType(proxy, target, method, retVal);

解析获取调用链

List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
1.获取各种advisors

Advisor[] advisors = config.getAdvisors();

2.遍历这些通知,如果是PointcutAdvisor,则会给他匹配对应的class,再注册Interceptors;如果是其他advisors,则直接添加方法拦截(。。。调试不到这。。)
3.如果调用链为空,则直接进行方法代理调用,也就是不做添加调用链操作的代理操作。
4.否则就创建一个新的CglibMethodInvocation()并且调用proceed();
这个方法可厉害了,链式调用!!!
解释都在代码里,只能帮到这里了。

public Object proceed() throws Throwable {
		// We start with an index of -1 and increment early.
		//当遍历的index等于拦截的通知数时,就会调用被代理对象的方法。
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		//每次调用都会增加一个index
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			//AspectJAroundAdvice
			//AspectJAfterThrowingAdvice
			//AspectJAfterReturningAdvice
			//AspectJAfterAdvice
			//AspectJMethodBeforeAdvice
			//以上这五个advice的调用。可以一个个调试看看
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				//如果调用匹配失败,则进入下一个调用
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

5.处理代理后的返回对象类型:retVal = processReturnType(proxy, target, method, retVal);
这好像没什么说的。

6.调用完之后就回到正常的业务代码上了。

AOP 就这么结束了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值