Spring Boot(十五):Spring擴展自定義Aop

前言

通過本篇文章,讓你瞭解什麼是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

 

 

 

 

 

 


 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章