加强对Aop的理解,自己实现一个简单版的Aop

手写AOP

实现思路

我们先看下我们平常是如何使用spring的Aop,下面代码是一个比较简单的aop的前置通知的使用。

@Component
@Aspect
public class BeforeAspect {

    @Before("execution(public * com.test.controller..*.*(..))")
    public void before(JoinPoint joinPoint) {
    }
}

首先我们看类注解,@Component@Aspect 第一个是将该类作为一个bean,交给容器去管理,第二个注解的意思就是告诉容器,我这个类是
一个Aop的类,在加载bean的时候需要做特定的操作。我们再来看方法上的注解@Before,这个是Aop的前置通知方法,表示我这个方法是所有符
合我定义的切面条件,在它们本身代码执行之前要先执行。然后注解里面就是定义的条件(具体意思就是所有com.test.controller的方法都需
符合我的条件,都需要执行下面这个方法),然后是方法的参数JoinPoint,这个类能够帮我们获取很多请求的信息,比如参数、请求的方法等
具体的自行去了解一下。

我们根据上面使用方法,来解析Aop是如何实现的,

首先定义一个类,使用特定的注解告诉Spring容器这个类是一个Aop类,然后通过注解告诉容器,在加载类的时候,符合我条件的我需要对它进行
方法增强,在执行原本方法之前或之后动态织入一些逻辑代码,然后再交给容器去管理。

思路上遇到的问题
  • 如何定义切面?
    我们就模仿Spring来写,首先使用一个注解标记该类是一个Aop类,它是使用@Before@Around@After来告诉容器具体织入的逻辑是在
    原方法的之前,还是之后,还是前后都需要执行。我们这里就做的简单一些,我们也定义四个注解@CustomerAop@Before@Around
    @After,通过@CustomerAop来告诉容器,这个类是Aop的类,然后@Before@Around@After分别表示方法执行之前、方法执行
    前后、方法执行后执行。Spring的Aop中条件匹配功能太多,有注解的,有匹配粒度到类的、甚至匹配的粒度还能到方法和参数、我们这里就做的简单
    点,匹配某个包下面的所有类注解如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {

    String value() default "";

}

我们先把我们的Aop的类写出来,然后再围绕这个类展开编写。具体代码如下:

@CustomerAop
@Component
public class AspectLogic {

    @Before(value = "cn.isuyu.customer.spring.aop.service")
    public void before() {
        System.out.println("before----------------------");
    }

    @After(value = "cn.isuyu.customer.spring.aop.service")
    public void after() {
        System.out.println("after----------------------");
    }

    @Around(value = "cn.isuyu.customer.spring.aop.service")
    public void around() {
        System.out.println("around----------------------");
    }

}
  • 在什么时候去匹配符合切面的bean

这个肯定是在容器扫描完所有类的时候,然后我们去判断哪些类加了我们的自定义注解@CustomerAop,然后我们去解析这个类,看下这个类有哪些
注解(@Before@Around@After),并看他们的匹配条件是啥。然后将他们都记录下来,问题又来了,我从哪能知道Spring容器已经将所
有的Bean加载完了,Spring有没有提供这种口子,我去实现预留的接口,然后把加载到的类全部返回回来,Spring还真有这种这种接口,一个叫
BeanFactoryPostProcessor的后置处理器,实现这个接口,会直接把Bean工厂都返回过来,我们拿到所有扫描到的beanName,然后去获取对应
BeanDefinition(spring用来描述Bean的数据接口,这个类可以获取Bean的作用域,是否懒加载,具体可以点开这个类去看),不过我们要的
不是这个类,而是这个类的子类AnnotatedBeanDefinition,因为我们是基于注解将类给容器管理的。

public class AopBeanFactoryPostProcess implements BeanFactoryPostProcessor {

    public static volatile Map<String, List<ProxyBeanHolder>> proxyBeanHolders = new ConcurrentHashMap<>();

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        //拿到容器中所有被加载的类名
        String[] beanDefinitionNames = configurableListableBeanFactory.getBeanDefinitionNames();
        //遍历获取到当前bean的容器中的数据结构BeanDefinition
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(beanDefinitionName);
            //因为BeanDefinition的子类有很多,因为我们是基于注解扫描的,所以我们要匹配AnnotatedBeanDefinition的Bean
            if (beanDefinition instanceof  AnnotatedBeanDefinition) {
                //拿到Bean类上修饰的注解
                AnnotationMetadata annotationMetadata = ((AnnotatedBeanDefinition) beanDefinition).getMetadata();
                //获取修饰类所有注解的名称集合
                Set<String> annotationTypes = annotationMetadata.getAnnotationTypes();
                for (String annotationType : annotationTypes) {
                    //筛选出加了标记为Aop类的Bean,然后去解析这个类
                    if (annotationType.equals(CustomerAop.class.getName())) {
                        doSacn((GenericBeanDefinition) beanDefinition);
                    }
                }
            }
        }
    }

    /**
     * 解析加了@CustomerAop的类
     * @param beanDefinition
     */
    private void doSacn(GenericBeanDefinition beanDefinition) {
        try {
            Class clazz = Class.forName(beanDefinition.getBeanClassName());
            //获取当前Bean所有的方法然后遍历判断是否加了自定义Aop的通知注解
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                //获取类上所有的
                Annotation[] annotations = method.getAnnotations();
                for (Annotation annotation : annotations) {
                    if(annotation.annotationType().getName().equals(Before.class.getName())
                    || annotation.annotationType().getName().equals(Around.class.getName())
                    || annotation.annotationType().getName().equals(After.class.getName())) {
                        doScan(method,annotation);
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析方法上的通知注解的值,
     * 然后将需要增强的类都找出来
     * 然后放到我们自己定义的数据
     * 结构中,存好以后方便
     * BeanPostProcess来使用
     * @param method
     * @param annotation
     */
    private void doScan(Method method,Annotation annotation){
        //获取注解所有的方法
        Method[] methods = annotation.annotationType().getMethods();
        //遍历注解的值得到需要增强的包路径
        //然后以类名为key,增强代码路径用ProxyBeanHolder来
        //修饰,然后将其添加到Map中
        for (Method annotationMethod : methods) {
            if (annotationMethod.getName().equals("value")) {
                try {
                    //得到通知注解上的包路径
                    String packageName = annotationMethod.invoke(annotation,null).toString();
                    if (!packageName.isEmpty()) {
                        //得到当前类的根目录
                        String rootPath = this.getClass().getResource("/").getPath().replaceAll("\\.","/");
                        //获取注解包下面所有的文件,然后将其类名为key存放到map中,这里会将该类需要增强的方法路径全部存到集合中
                        File file = new File(rootPath + packageName.replaceAll("\\.","/"));
                        for (File listFile : file.listFiles()) {
                            if (listFile.isFile()) {
                                String className = packageName + "." + listFile.getName().replaceAll(".class","");
                                if (!proxyBeanHolders.containsKey(className)) {
                                    List<ProxyBeanHolder> proxyBeanHolderList = new Vector<>();
                                    proxyBeanHolders.put(className,proxyBeanHolderList);
                                }
                                ProxyBeanHolder proxyBeanHolder = new ProxyBeanHolder();
                                proxyBeanHolder.setMethod(method.getName());
                                proxyBeanHolder.setAnnotationName(annotation.annotationType().getName());
                                proxyBeanHolder.setClassName(method.getDeclaringClass().getName());
                                proxyBeanHolders.get(className).add(proxyBeanHolder);
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 什么时候对bean进行代理

既然有能返回BeanFactory的后置处理器,那肯定也有返回Bean的后置处理器,我们在容器返回BeanFactory的时候,已经将所有的Aop类都添加到了
我们定义的Map集合中,现在我们需要做的就是将Bean返回回来,然后插手Bean的生成,通过CGLIB代理来将通知的代码来动态织入到类中,具体代码如下

/**
 * @Author NieZhiLiang
 * @Email nzlsgg@163.com
 * @GitHub https://github.com/niezhiliang
 * @Date 2020/6/8 上午11:50
 * 该类可以插手Bean的生成过程,就是对该类进行代理,
 * 然后返回容器一个代理类,后面我们每次拿到的都是
 * 代理类。
 */
public class AopBeanPostProcess implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
         String targetName = bean.getClass().getName();
         //判断当前bean是否存在需要增强的类Map中,如果存在我们就用CGLIB来进行代理,然后返回一个
        //代理类给容器,容器
         if (AopBeanFactoryPostProcess.proxyBeanHolders.containsKey(targetName)) {
             Enhancer enhancer = new Enhancer();
             enhancer.setSuperclass(bean.getClass());
             enhancer.setCallback(new MyMethodInterceptor(AopBeanFactoryPostProcess.proxyBeanHolders.get(targetName)));
             bean =  enhancer.create();
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

到此我们的手写的Aop已经完成了99%啦,实际上Spring的Aop是可以通过注解来控制打开还是关闭,我记得以前打开Aop还需要加上注解@EnableAspectJAutoProxy,
,实际上就是不加这个注解,我就不把我们写的后置处理器的类交给Spring容器管理,如果有这个注解我就把我自定义的处理器放到容器中嘛。

动态打开关闭Aop

动态添加bean到容器中,Spring自己的Aop使用的是注解,然后注解上使用了一个@Import,将一个实现了ImportBeanDefinitionRegistrar接口的类,动态添加到
容器中,ImportBeanDefinitionRegistrar这也是Spirng的后置处理器之一,我们这里使用Spring的另一个后置处理器,ImportSelector,这个也能动态把类交给
容器去处理。

Spring代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
    //控制是代理的实现方式 cglib和jdk动态代理
	boolean proxyTargetClass() default false;
    
	boolean exposeProxy() default false;

}
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        //注册了一个基于注解的自动代理创建器   AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
            //表示强制指定了要使用CGLIB
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
            //强制暴露Bean的代理对象到AopContext
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

}

我们也写一个自己的注解来控制Aop的打开和关闭,@EnableCustomerAop,我们这里就简单的做个开关功能。具体代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableCustomerAop {
}

动态将我们自定义的处理器交给容器管理的类:

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{AopBeanFactoryPostProcess.class.getName(), AopBeanPostProcess.class.getName()};
    }
}

参考:https://blog.csdn.net/baomw/article/details/84262006

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值