前言
通過本篇文章,讓你瞭解什麼是spring後置處理器,然後利用spring的後置處理器我們自己來手寫一個springAop,來完成和springAop一樣的功能!讓你可以對你的面試官說:我精通AOP!
1. spring後置處理器
今天呢我跟大家介紹的後置處理器有三個
BeanFactoryPostProcessor : 可以插手beanFactory的生命週期
BeanPostProcessor :可以插手bean的生命週期
ImportSelector :藉助@Import註解,可以動態實現將一個類是否交由spring管理,常用作開關操作
1.1 BeanFactoryPostProcessor
該接口只定義了一個方法,在我們beanFactory被創建出來後,相關準備工作做完後,會去執行invokeBeanFactoryPostProcessors(beanFactory);也就是去執行我們的BeanFactoryPostProcessor
以上可以看出,spring在執行
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
的時候,會傳入一個List beanFactoryPostProcessors;然後循環去執行list裏面所有實現了
BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor的對象的相關方法。
1.2 BeanPostProcessor
該接口定義了兩個方法,分別在bean實例化之後放到我們的容器之前和之後去執行,方法的返回值爲一個object,這個object就是我們存放在容器的對象了(所以這個位置我們可以對我們的bean做一個動態的修改,替換等等操作,所以這也是我們spring的擴展點之一)。
1.3 ImportSelector
在講ImportSelector之前,先講一下@Import這個註解。在spring處理我們的java類的時候,會分成四種情況去處理:
1)普通類:就是我們家裏@Component,@Service,@Repository等等的類
2)處理我們的import進來的類:
這裏呢,又分爲三種情況:
a)import一個普通類:@Import(A.class)
b)import一個Registrar:比如我們的aop @Import(AspectJAutoProxyRegistrar.class)
c)import一個ImportSelector:具體妙用見下文
對於普通類,spring在doScan的時候,就將掃描出來的java類轉換成我們的BeanDefinition,然後放入BeanDefinitionMap中,對於@import的三種情況,處理就在ConfigurationClassPostProcessor(該類是BeanDefinitionRegistryPostProcessor 後置處理器的一個實現,同時這也是我們spring內部自己維護的唯一實現類(排除內部類))。
2. 自定義Aop
2.1 模擬我們的springAop
模擬springAop,那麼我們就需要解決如下幾個問題:
A)我們知道開啓和關閉aop需要註解@EnableAspectJAutoProxy,如何實現,結合上文,我們可以使用@import(ImportSelector.class)
B)如何確定代理關係,即哪些是我們需要代理的目標對象和其中的目標方法,以及哪些方法是要增強到目標對象的目標方法上去的?
C)如何實現目標對象的替換,就是我們在getBean的時候,如何根據目標對象來獲取到我們增強後的代理對象?
2.2 要模擬aop,那麼我們就要結合我們怎麼去使用aop
對於AOP,我們知道有一個開關注解類 @EnableAspectJAutoProxy(同樣我們定義個@EnableAop)註解@Aspect,@Before,@After。。。(注意這些都不是spring的註解,是Aspectj的註解,只是我們spring直接引用了而已,同樣我們也對於新建自定義註解@MyAspect,@MyBefore,@MyAfter,@MyAround。。。)
@EnableAop
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(CustomizedAopImportSelector.class)
public @interface EnableAop {
}
@MyAspect
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
@MyBefore
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBefore {
String value() default "";
}
@MyAfter
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAfter {
String value() default "";
}
@MyAround
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAround {
String value() default "";
}
針對問題b,由於BeanFactoryPostProcessor的所有實現會在beanFactory完成對由於bean的掃描後,在實例化之前執行,所以我們可以新建一類,實現這個接口,然後實現方法裏面主要完成對所有BeanDefinition的掃描,找出我們所有的通知類,然後循環裏面的方法,找到所有的通知方法,然後根據註解判斷切入類型(也就是前置,後置還是環繞),最後解析註解的內容,掃描出所有的目標類,放入我們定義好的容器中。
具體實現如下:
a.定義handler,用於描述通知信息
/**
*
* 代理類的基礎信息--用於描述通知信息
* @author reyco
*
*/
public class ProxyBeanHandler {
/**
* 通知類名稱
*/
private volatile String className;
/**
* 通知方法名稱
*/
private volatile String methodName;
/**
* 註解類名稱
*/
private volatile String annotationName;
getter...
setter...
}
b.定義數據工具類,具體作用見註釋
/**
* 描述工具類
* @author aop的描述
*
*/
public class ConfigurationUtil {
/**
* aop標識註解類
*/
public static final String AOP_POINTCUT_ANNOTATION = "com.reyco.aop.core.annotation.MyAspect";
/**
* 前置通知註解類
*/
public static final String BEFORE = "com.reyco.aop.core.annotation.MyBefore";
/**
* 後置通知註解類
*/
public static final String AFTER = "com.reyco.aop.core.annotation.MyAfter";
/**
* 環繞通知註解類
*/
public static final String AROUND = "com.reyco.aop.core.annotation.MyAround";
/**
* 存放需代理的全部目標對象類
*/
public static volatile ConcurrentMap<String,List<ProxyBeanHandler>> classzzProxyBeanHandler = new ConcurrentHashMap<String, List<ProxyBeanHandler>>();
}
c. 定義我們的註冊類,用於註冊我們的目標對象和通知對象之間的關係,其核心代碼如下,首先實現BeanFactoryPostProcessor ,保證其實在對所有的bean完成掃描後,在bean的實例化之前執行,然後再其中按上述思路,scan出所有的目標對象,然後建立起目標對象和通知對象的關聯關係,然後放入我們的Map中.
public class RegisterBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
/**
* 存放需要代理的相關信息類
*/
public static volatile List<ProxyBeanHandler> proxyBeanHandlerList = new Vector<ProxyBeanHandler>();
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//獲取所有的beanDefinitionName
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanDefinitionName:beanDefinitionNames){
BeanDefinition beanDefinition= beanFactory.getBeanDefinition(beanDefinitionName);
//判斷beanDefinition是否是一個註解AnnotatedBeanDefinition
if (beanDefinition instanceof AnnotatedBeanDefinition) {
//取得beanDefinition上的所有註解
AnnotationMetadata metadata = ((AnnotatedBeanDefinition) beanDefinition).getMetadata();
Set<String> Annotations = metadata.getAnnotationTypes();
//循環所有註解,找到aop切面註解類
for (String annotation:Annotations)
if (annotation.equals(ConfigurationUtil.AOP_POINTCUT_ANNOTATION)) {
doScan((GenericBeanDefinition)beanDefinition);
}
}
}
}
/**
* 掃描所有註解方法
* @param beanDefinition
*/
private void doScan(GenericBeanDefinition beanDefinition){
try {
String className = beanDefinition.getBeanClassName();
Class<?> beanDefinitionClazz = Class.forName(className);
Method[] methods = beanDefinitionClazz.getMethods();
for (Method method :methods){
Annotation[] annotations = method.getAnnotations();
for(Annotation annotation:annotations) {
String annotationName = annotation.annotationType().getName();
if(annotationName.equals(ConfigurationUtil.BEFORE)||annotationName.equals(ConfigurationUtil.AFTER)||
annotationName.equals(ConfigurationUtil.AROUND))
doScan(className,method,annotation);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 掃描出所有被代理的類
* @param className
* @param method
* @param annotation
*/
private void doScan(String className,Method method,Annotation annotation){
ProxyBeanHandler proxyBeanHandler = new ProxyBeanHandler();
proxyBeanHandler.setClassName(className);
proxyBeanHandler.setMethodName(method.getName());
proxyBeanHandler.setAnnotationName(annotation.annotationType().getName());
//獲取註解上的所有方法
Method[] annotationMethods = annotation.annotationType().getDeclaredMethods();
String packagePath = null;
for (Method annotationMethod:annotationMethods) {
if (annotationMethod.getName().equals("value")){
try {
packagePath = (String) annotationMethod.invoke(annotation, null);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
if (!packagePath.isEmpty()){
String rootPath = this.getClass().getResource("/").getPath();
String targetPackagePath = rootPath + packagePath.replace(".","/");
File file = new File(targetPackagePath);
File[] fileList = file.listFiles();
List<ProxyBeanHandler> proxyBeanHandlerList = null;
for (File temp:fileList) {
if (temp.isFile()) {//判斷是否爲文件
String targetClass = packagePath+"."+temp.getName().replace(".class","");
try {
proxyBeanHandlerList = ConfigurationUtil.classzzProxyBeanHandler.get(targetClass);
}catch(Exception e){
}
if (proxyBeanHandlerList==null) {
proxyBeanHandlerList = new Vector<ProxyBeanHandler>();
}
proxyBeanHandlerList.add(proxyBeanHandler);
ConfigurationUtil.classzzProxyBeanHandler.put(targetClass,proxyBeanHandlerList);
}
}
}
}
}
如此問題B就得到了完美的解決.
針對問題C,我們可以利用BeanPostProcessor,在bean實例化之後,在放入容器之前,進行一個條件過濾,如果當前對象是我們的目標對象(即在我們定義好的Map中),則對對象進行代理,將目標對象替換成代理對象返回即可
(注:spring實現aop採用cglib和jdk動態代理兩種方式,@EnableAspectJAutoProxy(proxyTargetClass=true)可以加開關控制,如果不加,目標對象如果有實現接口,則使用jdk動態代理,如果沒有就採用cglib(因爲我們知道cglib是基於繼承的))
我們這裏實現,都簡單粗暴一點,統一採用cglib代理,這樣就可以完成對任意對象的代理了。
具體實現如下:
/**
*
* @author reyco
*
*/
public class RealizedAopBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
String targetClass = bean.getClass().getName();
Object object = bean;
// 判斷
if (ConfigurationUtil.classzzProxyBeanHandler.containsKey(targetClass)) {
// 包含,替換成代理類
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(new CustomizedProxyInterceptor(ConfigurationUtil.classzzProxyBeanHandler.get(targetClass)));
object = enhancer.create();
}
return object;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
/**
* 代理對象
* @author reyco
*
*/
public class CustomizedProxyInterceptor implements MethodInterceptor {
private List<ProxyBeanHandler> proxyBeanHandlerList;
public CustomizedProxyInterceptor(List<ProxyBeanHandler> proxyBeanHandlerList) {
this.proxyBeanHandlerList = proxyBeanHandlerList;
}
/**
* @param o 攔截類
* @param method 攔截方法
* @param objects
*/
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//處理前置及環繞前置通知
for (ProxyBeanHandler proxyBeanHandler: proxyBeanHandlerList) {
String annotationName = proxyBeanHandler.getAnnotationName();
if (annotationName.equals(ConfigurationUtil.BEFORE)||annotationName.equals(ConfigurationUtil.AROUND)) {
this.doProxy(proxyBeanHandler);
}
}
Object result = null;
try{
result = methodProxy.invokeSuper(o,args);
}catch (Exception e){
System.out.println("get ex:"+e.getMessage());
throw e;
}
//處理後置及環繞前置通知
for (ProxyBeanHandler proxyBeanHandler: proxyBeanHandlerList) {
String annotationName = proxyBeanHandler.getAnnotationName();
if (annotationName.equals(ConfigurationUtil.AFTER)||annotationName.equals(ConfigurationUtil.AROUND))
this.doProxy(proxyBeanHandler);
}
return result;
}
/**
* 處理代理操作
* @param proxyBeanHolder
*/
private void doProxy(ProxyBeanHandler proxyBeanHandler){
String className = proxyBeanHandler.getClassName();
String methodName = proxyBeanHandler.getMethodName();
Class<?> clazz = null;
try {
clazz = Class.forName(className);
Method[] methods = clazz.getMethods();
for (Method poxyMethod:methods)
if (poxyMethod.getName().equals(methodName)) {
poxyMethod.invoke(clazz.newInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
如此我們第三個問題也就順利解決了
最後還剩下我們的問題A,這時候就可以引出我們的@import(ImportSelector.class)了
ImportSelector 接口有一個實現方法,返回一個字符串類型的數組,裏面可以放類名,在@import(ImportSelector.class)的時候,spring會把我們返回方法裏面的類全部註冊到BeanDefinitionMap中,繼而將對象註冊到Spring容器中.
/**
* 描述:
* 自定義aop實現,提交給spring處理的類
*
* @author reyco
*/
public class CustomizedAopImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{RealizedAopBeanPostProcessor.class.getName(),RegisterBeanFactoryPostProcessor.class.getName()};
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(CustomizedAopImportSelector.class)
public @interface EnableAop {
}
很明顯,如果我的xxxApplication上加了@EnableAop註解,則會將我們的後置處理器的實現類交給了spring管理,spring才能去掃描得到這個類,才能去執行我們的自定義的後置處理器裏面的方法,才能實現我們的aop的代理,因此,我們的開關也就順利完成了。
3. 測試
TestApplication.java
@EnableAop
@SpringBootApplication
public class TestApplcation {
public static void main(String[] args) {
SpringApplication.run(TestApplcation.class, args);
}
}
TestService.java
@Service
public class TestService {
public void query() {
System.out.println("執行目標方法...");
}
}
TestAop.java
@Component
@MyAspect
public class TestAop {
@MyBefore("com.reyco.test.core.service")
public void testBefore() throws Throwable {
System.out.println("before -----------------");
}
@MyAfter("com.reyco.test.core.service")
public void testAfter() {
System.out.println("after ------------------");
}
@MyAround("com.reyco.test.core.service")
public void testAround() throws Throwable {
System.out.println("around -----------------");
}
}
TestController.java
@RestController
public class TestController {
@Autowired
TestService testService;
@RequestMapping("/test")
public String test() {
testService.query();
return "ok";
}
}
效果:
源碼地址:https://github.com/sihaihou/Resoures/tree/master/spring/springaop/aop