【Spring】重构--仿写Spring核心逻辑(五)实现AOP(aop包)

系列文章:

在前面三篇,我们已经成功实现了 Spring 最核心的功能 IOC和DI 以及 MVC,从这篇开始,我们就要来实现Spring的另一核心 AOP。

1.配置封装(config包)

下面这些类 AOP 中需要用到的基本组件,是对一些基本信息的封装。

MYAopConfig

封装了配置文件信息

public class MYAopConfig {

    private String pointCut;
    private String aspectBefore;
    private String aspectAfter;
    private String aspectClass;
    private String aspectAfterThrow;
    private String aspectAfterThrowingName;

    public String getPointCut() {
        return pointCut;
    }

    public void setPointCut(String pointCut) {
        this.pointCut = pointCut;
    }

    public String getAspectBefore() {
        return aspectBefore;
    }

    public void setAspectBefore(String aspectBefore) {
        this.aspectBefore = aspectBefore;
    }

    public String getAspectAfter() {
        return aspectAfter;
    }

    public void setAspectAfter(String aspectAfter) {
        this.aspectAfter = aspectAfter;
    }

    public String getAspectClass() {
        return aspectClass;
    }

    public void setAspectClass(String aspectClass) {
        this.aspectClass = aspectClass;
    }

    public String getAspectAfterThrow() {
        return aspectAfterThrow;
    }

    public void setAspectAfterThrow(String aspectAfterThrow) {
        this.aspectAfterThrow = aspectAfterThrow;
    }

    public String getAspectAfterThrowingName() {
        return aspectAfterThrowingName;
    }

    public void setAspectAfterThrowingName(String aspectAfterThrowingName) {
        this.aspectAfterThrowingName = aspectAfterThrowingName;
    }
}

2.切面组件(aspect包)

MYJoinPoint

封装了 Spring Aop 中切面方法的信息,在切面方法中添加 JoinPoint 参数就可以获取到封装了该方法信息的 JoinPoint 对象,会作为使用时所有Advice的入参

public interface MYJoinPoint {

    Object getThis();

    Object[] getArguments();

    Method getMethod();

    void setUserAttribute(String key, Object value);

    Object getUserAttribute(String key);
}

MYAdvice

通知类的顶层接口

public interface MYAdvice {
}

MYMethodInterceptor

拦截器链的组件的统一接口

public interface MYMethodInterceptor {

    Object invoke(MYMethodInvocation invocation) throws Throwable;
}

MYMethodBeforeAdviceInterceptor

拦截器链组件,前置通知

public class MYMethodBeforeAdviceInterceptor extends MYAbstractAspectAdvice implements MYMethodInterceptor {

    private MYJoinPoint joinPoint;

    public MYMethodBeforeAdviceInterceptor(Method aspectMethod, Object aspectTarget) {
        super(aspectMethod, aspectTarget);
    }

    private void before(Method method,Object[] args,Object target) throws Throwable{
        // 传送了给织入参数
        // method.invoke(target);
        super.invokeAdviceMethod(this.joinPoint,null,null);
    }

    @Override
    public Object invoke(MYMethodInvocation mi) throws Throwable {
        // 从被织入的代码中才能拿到,JoinPoint
        this.joinPoint = mi;
        before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }
}

MYAfterReturningAdviceInterceptor

拦截器链组件,返回通知

public class MYAfterReturningAdviceInterceptor extends MYAbstractAspectAdvice implements MYMethodInterceptor {

    private MYJoinPoint joinPoint;

    public MYAfterReturningAdviceInterceptor(Method aspectMethod, Object aspectTarget) {
        super(aspectMethod, aspectTarget);
    }

    @Override
    public Object invoke(MYMethodInvocation mi) throws Throwable {
        Object retVal = mi.proceed();
        this.joinPoint = mi;
        this.afterReturning(retVal,mi.getMethod(),mi.getArguments(),mi.getThis());
        return retVal;
    }

    private void afterReturning(Object retVal, Method method, Object[] arguments, Object aThis) throws Throwable {
        super.invokeAdviceMethod(this.joinPoint,retVal,null);
    }
}

MYAfterThrowingAdviceInterceptor

拦截器链组件,异常通知

public class MYAfterThrowingAdviceInterceptor extends  MYAbstractAspectAdvice implements MYMethodInterceptor {

    private String throwingName;

    public MYAfterThrowingAdviceInterceptor(Method aspectMethod, Object aspectTarget) {
        super(aspectMethod, aspectTarget);
    }

    public void setThrowName(String throwingName) {
        this.throwingName = throwingName;
    }

    @Override
    public Object invoke(MYMethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        }catch (Throwable e){
            invokeAdviceMethod(mi,null,e.getCause());
            throw e;
        }
    }
}

3. 拦截器链(intercept包)

MYAbstractAspectAdvice

拦截器链执行通用逻辑,实现了 MYAdvice 接口

public class MYAbstractAspectAdvice implements MYAdvice{

    private Method aspectMethod;
    private Object aspectTarget;
    
    public MYAbstractAspectAdvice(Method aspectMethod, Object aspectTarget) {
        this.aspectMethod = aspectMethod;
        this.aspectTarget = aspectTarget;
    }
    
    public Object invokeAdviceMethod(MYJoinPoint joinPoint, Object returnValue, Throwable tx) throws Throwable{
        Class<?> [] paramTypes = this.aspectMethod.getParameterTypes();
        if(null == paramTypes || paramTypes.length == 0){
            return this.aspectMethod.invoke(aspectTarget);
        }else{
            Object [] args = new Object[paramTypes.length];
            for (int i = 0; i < paramTypes.length; i ++) {
                if(paramTypes[i] == MYJoinPoint.class){
                    args[i] = joinPoint;
                }else if(paramTypes[i] == Throwable.class){
                    args[i] = tx;
                }else if(paramTypes[i] == Object.class){
                    args[i] = returnValue;
                }
            }
            return this.aspectMethod.invoke(aspectTarget,args);
        }
    }
}

MYMethodInvocation

拦截器链执行器

public class MYMethodInvocation implements MYJoinPoint {

    // 代理对象
    private Object proxy;
    // 目标类
    private Class<?> targetClass;
    // 目标对象
    private Object target;
    // 要被处理方法(不一定是增强方法)
    private Method method;
    // 方法的参数们
    private Object [] arguments;
    // 拦截器链
    private List<Object> interceptorsAndDynamicMethodMatchers;

    // 定义一个索引,从-1开始来记录当前拦截器执行的位置
    private int currentInterceptorIndex = -1;

    private Map<String, Object> userAttributes;

    public MYMethodInvocation(
            Object proxy, Object target, Method method, Object[] arguments,
            Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {

        this.proxy = proxy;
        this.target = target;
        this.targetClass = targetClass;
        this.method = method;
        this.arguments = arguments;
        this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
    }

    @Override
    public Object getThis() {
        return this.target;
    }

    @Override
    public Object[] getArguments() {
        return this.arguments;
    }

    @Override
    public Method getMethod() {
        return this.method;
    }

    public void setUserAttribute(String key, Object value) {
        if (value != null) {
            if (this.userAttributes == null) {
                this.userAttributes = new HashMap<String,Object>();
            }
            this.userAttributes.put(key, value);
        }
        else {
            if (this.userAttributes != null) {
                this.userAttributes.remove(key);
            }
        }
    }

    public Object getUserAttribute(String key) {
        return (this.userAttributes != null ? this.userAttributes.get(key) : null);
    }
}

proceed()

执行拦截器链

public Object proceed() throws Throwable {
    // 如果Interceptor执行完了,则执行joinPoint
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return this.method.invoke(this.target,this.arguments);
    }

    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    // 如果要动态匹配joinPoint
    if (interceptorOrInterceptionAdvice instanceof MYMethodInterceptor) {
        MYMethodInterceptor mi = (MYMethodInterceptor) interceptorOrInterceptionAdvice;
        return mi.invoke(this);
    } else {
        // 动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor
        return proceed();
    }
}

4.代理封装(support包)

MYAdvisedSupport

主要封装了被代理对象信息,以及一些处理被代理对象的方法。

每个IOC容器中对象都一一对应一个AdvisedSupport对象,但只有符合pointCut的对象才是真正的target

public class MYAdvisedSupport {
    // 目标类
    private Class<?> targetClass;
    // 目标对象,即被代理对象
    private Object target;
    // 配置文件信息
    private MYAopConfig config;
    // 被代理类特征正则表达式
    private Pattern pointCutClassPattern;
    // 保存当前对象切入点方法们,与拦截器链的映射关系
    private transient Map<Method, List<Object>> methodCache;

    public MYAdvisedSupport(MYAopConfig config) {
        this.config = config;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    // 将当前类放入AdvisedSupport对象中,同时初始化pointCutClassPattern(被代理类特征正则)
    public void setTargetClass(Class<?> targetClass) {
        this.targetClass = targetClass;
        parse();
    }

    public Class<?> getTargetClass() {
        return this.targetClass;
    }

    public Object getTarget() {
        return this.target;
    }
	
	//.......
}

parse()

会在创建代理时 setTargetClass 方法中调用,

  1. 初始化目标类特征 pointCutClassPattern
  2. 保存切面类的所有方法(LogAspect),将它们放入 aspectMethods 的Map中
  3. 为当前类中符合切入点的方法构造拦截器链,并将它们放入map中
private void parse() {
    String pointCut = config.getPointCut()
            .replaceAll("\\.","\\\\.")
            .replaceAll("\\\\.\\*",".*")
            .replaceAll("\\(","\\\\(")
            .replaceAll("\\)","\\\\)");
    // pointCut=public .* com.xupt.yzh.demo.service..*Service..*(.*)
    // 1.通过正则表达式获取被代理类特征,初始化pointCutForClassRegex
    String pointCutForClassRegex = pointCut.substring(0,pointCut.lastIndexOf("\\(") - 4);
    pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(
            pointCutForClassRegex.lastIndexOf(" ") + 1));

    try {
        // 2.拿到切面类,这里是LogAspect,并保存其所有方法
        Class aspectClass = Class.forName(this.config.getAspectClass());
        // 保存切面类(LogAspect)的所有方法
        Map<String,Method> aspectMethods = new HashMap<String,Method>();
        for (Method m : aspectClass.getMethods()) {
            aspectMethods.put(m.getName(),m);
        }

        methodCache = new HashMap<Method, List<Object>>();
        // 拿到切入点正则,即切入方法特征
        Pattern pattern = Pattern.compile(pointCut);

        // 3.遍历当前类的所有方法,为符合切入点的方法构造拦截器链,最后放入map中
        for (Method m : this.targetClass.getMethods()) {
            String methodString = m.toString();
            if (methodString.contains("throws")) {
                methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
            }

            // 判断当前方法是否是切点方法
            Matcher matcher = pattern.matcher(methodString);
            if(matcher.matches()){
                // 若是,则构造一个执行器链,
                List<Object> advices = new LinkedList<Object>();
                // 把每一个方法包装成 MethodIterceptor
                // before
                if(!(null == config.getAspectBefore() || "".equals(config.getAspectBefore()))) {
                    //创建一个Advivce
                    advices.add(new MYMethodBeforeAdviceInterceptor(aspectMethods.get(config.getAspectBefore()),aspectClass.newInstance()));
                }
                // after
                if(!(null == config.getAspectAfter() || "".equals(config.getAspectAfter()))) {
                    //创建一个Advivce
                    advices.add(new MYAfterReturningAdviceInterceptor(aspectMethods.get(config.getAspectAfter()),aspectClass.newInstance()));
                }
                // afterThrowing
                if(!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow()))) {
                    //创建一个Advivce
                    MYAfterThrowingAdviceInterceptor throwingAdvice =
                            new MYAfterThrowingAdviceInterceptor(
                                    aspectMethods.get(config.getAspectAfterThrow()),
                                    aspectClass.newInstance());
                    throwingAdvice.setThrowName(config.getAspectAfterThrowingName());
                    advices.add(throwingAdvice);
                }
                // 最后,将当前方法与执行器链放入mthodCache
                methodCache.put(m,advices);
            }

        }
    }catch (Exception e){
        e.printStackTrace();
    }

}

pointCutMatch()

判断当前AdvisedSupport是否能匹配上切点表达式的类

public boolean pointCutMatch() {
    // 通过被代理类特征正则表达式来匹配当前类
    return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
}

getInterceptorsAndDynamicInterceptionAdvice()

获取指定方法的执行器链

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) throws Exception{
    List<Object> cached = methodCache.get(method);

    // 如果当前方法没有拦截器链,那么把拦截器链置为null,也封装一下,然后返回原方法
    if (cached == null) {
        Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
        // cached 就等于原方法
        cached = methodCache.get(m);
        // 底层逻辑,对代理方法进行一个兼容处理
        this.methodCache.put(m, cached);
    }

    return cached;
}

到此为止 aop 包中的代码就实现完了,但是还有一步没做。

5.代理实现(aop核心)

MYAopProxy

AOP 本质就是一个代理模式,MYAopProxy 是代理的最顶层接口,有两种实现(JDK,cglib)

public interface MYAopProxy {

    Object getProxy();

    Object getProxy(ClassLoader classLoader);
}

MYCglibAopProxy

基于 Cglib 生成代理对象,因为 JDK 的动态代理有局限性(必须有接口或继承关系)

public class MYCglibAopProxy implements MYAopProxy {
    @Override
    public Object getProxy() {
        return null;
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return null;
    }
} 

cglib 代理这里先不做实现…

MYJdkDynamicAopProxy

基于 JDK 动态代理生成代理对象

public class MYJdkDynamicAopProxy implements MYAopProxy, InvocationHandler {

    private MYAdvisedSupport advised;

    public MYJdkDynamicAopProxy(MYAdvisedSupport config) {
        this.advised = config;
    }

    //......
}

getProxy()

获取代理对象

// 通过该方法对外返回Proxy
@Override
public Object getProxy() {
    // 调用传入ClassLoader的方法,创建Proxy
    return getProxy(this.advised.getTargetClass().getClassLoader());
}

// 通过jdk动态代理具体生成代理对象
@Override
public Object getProxy(ClassLoader classLoader) {
    // newProxyInstance方法会根据被代理对象ClassLoader,class对象,及增强逻辑(InvocationHanlder.invoke)生成一个代理对象
    // 注:动态代理只能代理有接口对象,本质原因是单继承
    return Proxy.newProxyInstance(classLoader, this.advised.getTargetClass().getInterfaces(), this);
}

invoke()

当 doDispatch 将请求分发给当前方法后执行

  1. 这个方法是在调用后执行,不是在初始化时执行,初始化时只是通过AdvisedSupport创建了拦截器链
  2. 对外是invoke原对象的方法,但实际上执行invoke代理对象的方法
  3. 这里传入要执行的method,可以对进行判断进行选择性增强
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 在AdvisedSupport中获取方法的拦截器链
    // 其中符合切点表达式的方法会有拦截器链,而不是切点的方法就只有自己
    List<Object> interceptorsAndDynamicMethodMatchers = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, this.advised.getTargetClass());
    // 获取拦截器链执行器
    MYMethodInvocation methodInvocation = new MYMethodInvocation(proxy, this.advised.getTarget(), method, args, this.advised.getTargetClass(), interceptorsAndDynamicMethodMatchers);
    // 执行拦截器链及invoke当前方法
    return methodInvocation.proceed();
}

6.入口:MyApplicationContext

在 ApplicationContext#instantiteBean 创建实例对象时,我们需要将原本的对象变为代理对象放入 IOC 容器。

instantiteBean()

private MYBeanWrapper instantiteBean(String beanName, MYBeanDefinition myBeanDefinition) {
    // 1.拿到要实例化的类名
    String className = myBeanDefinition.getBeanClassName();
    // 2.通过反射进行实例化
    Object instance = null;
    try {
        // 默认所有对象都是单例的,即都可以通过单例IOC容器中获取Bean 
        if (this.factoryBeanObjectCache.containsKey(className)) {
            instance = this.factoryBeanObjectCache.get(className);
        } else {
            Class<?> clazz = Class.forName(className);
            instance = clazz.newInstance();
            
            this.factoryBeanObjectCache.put(myBeanDefinition.getFactoryBeanName(), instance);

			//-------------------------AOP部分入口代码-----------------------
            // 读取配置文件中的信息,并以当前对象信息构建一个AdvisedSupport对象
            MYAdvisedSupport config = instantionAopConfig(myBeanDefinition);
            config.setTargetClass(clazz);
            config.setTarget(instance);
            // 符合PointCut的规则的话(即符合被代理的对象要求),创建将代理对象
            // 然后用代理对象替换当前对象,并放入IOC容器,
            // 到时 mvc 初始化IOC容器时,就会将代理对象放入,再后来创建处理请求的handler时就会将该代理对象封装进去
            if(config.pointCutMatch()) {
                // 这时获取到的 Proxy 持有的AdvisedSupport已经构造好了拦截器链
                // 到时 mvc 分发请求过来直接 proceed 执行即可
                instance = createProxy(config).getProxy();
            }
            //----------------------------------------------------------------

            // 注:这里还要根据className(全类名)放一个
            // 因为在通过类型进行getBean时,BeanDefinition只封装了接口做factoryName
            this.factoryBeanObjectCache.put(className, instance);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    // 3.封装BeanWrapper
    // 注:无论单例多例,都要先封装成 BeanWrapper
    MYBeanWrapper beanWrapper = new MYBeanWrapper(instance);
    return beanWrapper;
}

instantionAopConfig()

// 读取配置文件信息,封装成AopCofig后交给AopSupport去处理
private MYAdvisedSupport instantionAopConfig(MYBeanDefinition myBeanDefinition) {
    MYAopConfig config = new MYAopConfig();
    config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
    config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
    config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
    config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
    config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
    config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));

    return new MYAdvisedSupport(config);
}

createProxy()

// 创建代理对象
private MYAopProxy createProxy(MYAdvisedSupport config) {
    Class<?> targetClass = config.getTargetClass();
    // 当前类存在接口时,使用jdk的动态代理
    if (targetClass.getInterfaces().length > 0) {
        return new MYJdkDynamicAopProxy(config);
    }
    // 否则使用 cglib 代理(未实现)
    return new MYCglibAopProxy();
}

到此 aop 部分实现完成,下面我们来测试一下。

测试

在配置文件中,我们设置的是

# 切点表达式
pointCut=public .* com.xupt.yzh.demo.service..*Service..*(.*)
# 切面类
aspectClass=com.xupt.yzh.demo.aspect.LogAspect
# 前置通知
aspectBefore=before
# 后置通知
aspectAfter=after
# 异常通知
aspectAfterThrow=afterThrowing
# 异常类型
aspectAfterThrowingName=java.lang.Exception

也就是说 service 包下的所有方法都会被增强,包括前置通知,后置通知和异常通知。切面如下:

@Slf4j // 借助 slf4j 打印日志
public class LogAspect {

    // 在调用一个方法之前,执行before方法
    public void before(MYJoinPoint joinPoint){
        joinPoint.setUserAttribute("startTime_" + joinPoint.getMethod().getName(),System.currentTimeMillis());
        // 这个方法中的逻辑,是由我们自己写的
        log.info("Invoker Before Method!!!"  // 打印 Invoker Before Method!!!
                + "\nTargetObject:" +  joinPoint.getThis() // 打印请求对象
                +" \nArgs:" + Arrays.toString(joinPoint.getArguments())); // 打印请求参数
    }

    // 在调用一个方法之后,执行after方法
    public void after(MYJoinPoint joinPoint){
        log.info("Invoker After Method!!!" +
                "\nTargetObject:" +  joinPoint.getThis() +
                "\nArgs:" + Arrays.toString(joinPoint.getArguments()));
        long startTime = (Long) joinPoint.getUserAttribute("startTime_" + joinPoint.getMethod().getName());
        long endTime = System.currentTimeMillis();
        // 打印执行方法执行时间
        System.out.println("use time :" + (endTime - startTime));
    }
	
	// 异常通知
    public void afterThrowing(MYJoinPoint joinPoint, Throwable ex){
        log.info("出现异常" +
                "\nTargetObject:" +  joinPoint.getThis() +
                "\nArgs:" + Arrays.toString(joinPoint.getArguments()) +
                "\nThrows:" + ex.getMessage());
    }
}

结果如下:

在这里插入图片描述
可以看到已经成功实现了 AOP 功能!

完整代码我放到 GitHub 上了,可以点击这里跳转…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

A minor

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

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

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

打赏作者

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

抵扣说明:

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

余额充值