spring系列4-AOP的实现

目录

一、手动编码实现spring-aop

1. demo代码

2. 实现原理

2.1 如何生成代理

2.2 如何执行切面逻辑

3. spring-aop的高级特性

二、注解实现spring-aop

1. demo代码

2. 实现原理

参考资料


一、手动编码实现spring-aop

1. demo代码

(1)aop相关实体类:

advice接口的实现类:描述了aop代理要做什么事;

pointcut接口的实现类:描述了aop代理在哪里做事;

advisor则是对advice、pointcut的整合。

package com.abc;

//工人接口
public interface Worker {
	void work();
}
//普通打工人
public class OrdinaryWorker implements Worker {
	@Override
	public void work() {
		System.out.println("* * * * * 搬砖 * * * * *");
	}
}
//难过的打工人
public class SadWorker implements Worker {
	@Override
	public void work() {
		System.out.println("* * * * 咬牙搬砖 * * * *");
	}
}
//资本家类
public class Capitalist {
	public void work() {
		System.out.println("$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $");
	}
}
//工人增强器
public class WorkerAdvisor extends AbstractPointcutAdvisor {

	@Override
	public Advice getAdvice() {
		return new WorkerAdvice();
	}

	@Override
	public Pointcut getPointcut() {
		String pattern = "com.abc.OrdinaryWorker.*";
		JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
		pointcut.setPattern(pattern);
		return pointcut;
	}

	static class WorkerAdvice implements AfterReturningAdvice, MethodBeforeAdvice {
		@Override
		public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
			System.out.println("21:00 - 滴,下班:没有困难的工作,只有勇敢的打工人!");
		}
		@Override
		public void before(Method method, Object[] args, Object target) throws Throwable {
			System.out.println("9:00 - 滴,上班:早安打工人!");
		}
	}
}
//资本家增强器
public class CapitalistAdvisor extends AbstractPointcutAdvisor {

	@Override
	public Advice getAdvice() {
		return new CapitalistAdvice();
	}

	@Override
	public Pointcut getPointcut() {
		String pattern = "com.mytest.aop.bean.Capitalist.*";
		JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
		pointcut.setPattern(pattern);
		return pointcut;
	}

	static class CapitalistAdvice implements AfterReturningAdvice {
		@Override
		public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
			System.out.println("又是愉快的一天 (*^▽^*) ");
		}
	}
}

(2)spring配置文件aopxml.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="workerAdvisor" class="com.mytest.aop.bean.WorkerAdvisor"/>
	<bean id="capitalistAdvisor" class="com.mytest.aop.bean.CapitalistAdvisor"/>

	<bean id="worker" class="com.mytest.aop.bean.OrdinaryWorker"/>
	<bean id="capitalist" class="com.mytest.aop.bean.Capitalist"/>

	<bean id="aopWorker" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target">
			<ref bean="worker"></ref>
		</property>
		<property name="interceptorNames">
			<list>
				<value>workerAdvisor</value>
				<value>capitalistAdvisor</value>
			</list>
		</property>
		<property name="targetSource">
			<ref bean="hotSwappableTargetSource"/>
		</property>
	</bean>

	<bean id="aopCapitalist" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target">
			<ref bean="capitalist"></ref>
		</property>
		<property name="interceptorNames">
			<list>
				<value>workerAdvisor</value>
				<value>capitalistAdvisor</value>
			</list>
		</property>
	</bean>

	<bean id="hotSwappableTargetSource" class="org.springframework.aop.target.HotSwappableTargetSource">
		<constructor-arg ref="worker"/>
	</bean>

</beans>

(3)执行逻辑:

public class AopTest {

	public static void main(String[] args) {
		BeanFactory bf = new XmlBeanFactory(new ClassPathResource("mytest/aop/aopxml.xml"));
		Object aopWorker = bf.getBean("aopWorker");
		Object aopCapitalist = bf.getBean("aopCapitalist");

		assert !(aopCapitalist instanceof Proxy);
		assert aopWorker instanceof Proxy;

		Capitalist capitalist = (Capitalist) aopCapitalist;
		capitalist.work();
		System.out.println("\n普通的打工人:");
		Worker worker = (Worker) aopWorker;
		worker.work();

		System.out.println("\n难过的打工人:");
		SadWorker sadWorker = new SadWorker();
		HotSwappableTargetSource targetSource = bf.getBean("hotSwappableTargetSource", HotSwappableTargetSource.class);
		targetSource.swap(sadWorker);
		worker.work();
	}

}

(4)执行结果:

$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $
又是愉快的一天 (*^▽^*) 

普通的打工人:
9:00 - 滴,上班:早安打工人!
* * * * * 搬砖 * * * * *
21:00 - 滴,下班:没有困难的工作,只有勇敢的打工人!

难过的打工人:
9:00 - 滴,上班:早安打工人!
* * * * 咬牙搬砖 * * * *
21:00 - 滴,下班:没有困难的工作,只有勇敢的打工人!

问题:为什么断言能顺利执行 —— aopCapitalist不是Proxy的实例,而aopWorker是Proxy的实例???且看下面分解……

2. 实现原理

下面从两个角度分析aop的实现,一是如何生成代理对象,二是代理对象如何执行切面逻辑,也就是在业务逻辑中织入切面逻辑。

2.1 如何生成代理

spring是如何生成目标对象的代理,即解读:Object aopWorker = bf.getBean("aopWorker");

(1)ProxyFactoryBean的UML

"aopWorker"的类型是ProxyFactoryBean,UML图如下:

根据1:ProxyFactoryBean是工厂Bean,因此,执行bf.getBean("aopWorker")会调用ProxyFactoryBean的getObject方法;

根据2:ProxyFactoryBean能感知自己所在的BeanFactory,这是因为初始化bean时,会执行invokeAwareMethods,对各种aware类接口的实现类进行处理;

根据3:ProxyFactoryBean继承AdvisedSupport,有几个关键的成员变量:targetSource(被代理的目标对象的包装)、interfaces(代理的接口)、advisors(增强器)

(2)ProxyFactoryBean的getObject()

主要逻辑如下:

从上图的逻辑,可以知道为什么demo中两个代理对象的断言不同:这是因为Capitalist类没有实现接口,采用cglib创建代理对象,而aopWorker是基于jdk的动态代理。

考虑到继承关系,通过spring的property标签,既可以配置诸如:interceptorNames、targetName等来自ProxyFactoryBean的成员变量,也可以配置诸如targetSource、interfaces、advisors等来自AdvisedSupport的成员变量。

无论是基于jdk动态代理的JdkDynamicAopProxy,还是基于cglib的ObjenesisCglibAopProxy,都有1个成员变量:AdvisedSupport advised,记录了代理目标的配置信息。

2.2 如何执行切面逻辑

spring生成目标对象的代理后,代理对象是如何执行aop的切面逻辑,即解读:((Worker) aopWorker).work()。

从1.2节可知,AopProxy有2种实现:JdkDynamicAopProxy、ObjenesisCglibAopProxy,下面就分析一下JdkDynamicAopProxy的情况:

Java动态代理对象执行被代理的接口方法时,会执行InvocationHandler接口的invoke()方法。

JdkDynamicAopProxy实现了InvocationHandler接口,invoke()方法的主要逻辑如下:

从整体大局了解代码逻辑后,很自然的明白为什么chain的泛型是Object,而不是Interceptor,因为元素还可能是InterceptorAndDynamicMethodMatcher类型。而isRuntime这种情况,应该是针对方法重载的情况,相同的方法签名信息,入参类型不同,需要运行时再判断是否匹配。

aop对于不同advice转成不同MethodInterceptor,采用了适配器模式。aop的几个关键元素关系大致如下:

3. spring-aop的高级特性

了解spring-aop的基本实现后,就能够理解targetSource属性的意义:为什么用targetSource包装被代理的目标对象target?

开发者可以实现TargetSource接口,实现定制功能,比如demo中的HotSwappableTargetSource提供了热交换能力。

二、注解实现spring-aop

1. demo代码

(1)切面相关:

@Aspect
public class SleepAspect {

	@Pointcut("execution(* *.work(..))")
	public void pointCutExpression() {
	}

	@Around("pointCutExpression()")
	public Object around(ProceedingJoinPoint jp){
		Object result = null;
		try{
			result = jp.proceed();
			System.out.println("z z z z z 晚安 z z z z z");
		} catch (Throwable t){
		}
		return result;
	}
}

(2)spring配置文件aopannotation.xml:

<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">

	<aop:aspectj-autoproxy/>
	<bean class="com.mytest.aop.aspect.SleepAspect"></bean>

	<bean id="worker" class="com.mytest.aop.bean.OrdinaryWorker"/>
	<bean id="capitalist" class="com.mytest.aop.bean.Capitalist"/>

</beans>

(3)执行逻辑:

public class AopAnnotationTest {

	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("mytest/aop/aopannotation.xml");
		Worker worker = (Worker) ac.getBean("worker");
		worker.work();
		System.out.println();
		Capitalist capitalist = (Capitalist) ac.getBean("capitalist");
		capitalist.work();
	}
}

(4)执行结果:

* * * * * 搬砖 * * * * *
z z z z z 晚安 z z z z z

$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $
z z z z z 晚安 z z z z z

2. 实现原理

注解实现aop与手动编码实现aop的差别在于代理对象的创建,下图描述了代理对象创建的主要流程:

 

参考资料

“手动编码实现spring-aop”:参考《spring技术内幕(第2版)》

“注解实现spring-aop”:参考《spring源码深度解析(第2版)》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值