Spring Boot(十五):Spring扩展自定义Aop

15 篇文章 0 订阅
2 篇文章 0 订阅

前言

通过本篇文章,让你了解什么是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


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java的艺术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值