Spring源码解析(5)

Aop前置知识

Aop思想的核心:动态代理思想的应用,涉及到的设计模式是代理设计模式。代理设计模式就是通过代理类为目标类进行额外功能的开发,这样设计的好处:解耦合,利于原始类的维护。

代理设计模式vs装饰器设计模式

  • 代理设计模式需要增强的功能是额外功能,和原始类中的业务功能没有太大关系。装饰器设计模式是对本职功能的增强。例如转账方法中进行事务的增强,事务只是保证整个转账方法的完整,转账方法和增强的事务方法没有太大的关系,这个就是代理设计模式的体现。在Mybatis里面的一级缓存就是对于查询进行了增强,这个是装饰器设计模式的体现。
  • 代理设计模式基于动态字节码实现的,例如JDK,CGLIB,装饰器设计模式实现没有使用动态字节码技术

Spring中进行AOP开发的实现步骤:

  1. 定义目标类,或者称为业务类
  2. 定义额外的功能,或者称为通知
  3. 明确切入点,或称为对哪个业务方法进行增强
  4. 定义切面,通过生成代理对象来完成目标方法被通知(或者增强方法)进行增强

实现方式

  • 基于XML的实现方式,在Spring配置文件中使用XML定义AOP代理和切入点信息。

  • 基于注解的方式,通常配置一个类,使用@Configuration和@EnableAspectJAutoProxy启用注解的Aop支持


    下面演示一下基于注解的方式实现Aop,这里是用户新增之前开启一个权限的校验,因此使用前置通知(@Before)

java 复制代码
package com.example.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = "com.example")
@EnableAspectJAutoProxy // 开启基于注解的aop模式
public class AppConfig {
}

---------------------------------------------------------------------
package com.example.service;

public interface UserService {
    public void add();
}

---------------------------------------------------------------------
package com.example.service.impl;

import com.example.service.UserService;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("add方法实现了");
    }
}

---------------------------------------------------------------------
package com.example.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect {
    @Before("execution(* com.example.service.impl.UserServiceImpl.*(..))")
    public void check() {
        System.out.println("开启权限校验。。。");
    }
}

---------------------------------------------------------------------
package com.example.test;

import com.example.config.AppConfig;
import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class testAop {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService =(UserService) context.getBean("userService");
        userService.add();
    }
}

目前是通过代理对象的产生,对于切点方法的增强,因此UserService是一个代理对象,

如果使用CGLIB动态代理,将@EnableAspectJAutoProxy中的proxyTargetClass改为true,默认为false,使用的是jdk底层的动态代理。

JDK动态代理vsCGLIB字节码代理

  1. JDK动态代理要求目标类必须实现接口,通过字节码技术对接口进行实现,既保留目标类的原始功能,又对原始功能进行增强
  2. CGLIB根据目标类通过动态代理技术创建目标类的子类,在子类方法中,既保留目标类的原始功能,又对原始功能进行增强
  3. 如果没有接口,默认使用CDLIB,不能切换为JDK
  4. JDK动态代理依靠内置的Proxy类中的newProxyInstance方法,参数有类加载器,原始类的字节码文件的数组,命名对象InvocationHandler,其内部有invoke方法,在invoke方法内部实现了目标方法的增强。invoke方法中有代理对象proxy,增强方法的引用以及执行方法需要传递的参数
  5. CGLIB需要引入第三方支持,基于Enhancer,其中有create方法,方法中参数有目标类的字节码对象,以及对目标对象增强的方法MethodInterceptor

Spring中对两种代理对象的实现方式进行了整合,通过BeanPostProcessor进行代理对象的产生,对Bean进行增强

java 复制代码
package com.example.processor;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@Component

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("开启前置增强的操作");
                return method.invoke(bean, args);
            }
        });
    }
}

上述代码中的实现方式是对原始类中的所有方法都进行增强,对目标类某种方法进行增强,这种方式是不合理的,Spring中的AspectJ是可以对某个方法进行增强

源码解析

关掉动态代理之后,进入AbstractApplicationContext类,refresh方法中有个this.registerBeanPostProcessors(beanFactory);表示向beanFactory注册BeanPostProcessors。动态代理开关进行两者的对比

AnnotationAwareAspectJAutoProxyCreator就是bean代理对象产生的BeanPostProcssor,是通过注解@EnableAspectJAutoProxy注入的

那么创建代理对象,是在什么时候进行创建的?

AbstractAutowireCapableBeanFactory.class中有个createBean方法,这个方法调用this.createBean方法,createBean方法中有一个this.doCreateBean方法,doCreateBean方法里有创建对象,属性赋值以及初始化,在初始化这行右键,增加条件断点

此时的beanName就是userService,进入初始化方法, 由于bean代理对象的产生是在后置处理方法上,进入this.applyBeanPostProcessorsAfterInitialization(),

此时用的是jdk动态代理

进入warpIfNecessary方法中的

Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));进行代理对象的产生,进入createProxy方法中,有buildProxy,在进入这个buildProxy,return 的是classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader);进入这个getProxy(),进入后,return this.createAopProxy().getProxy(classLoader),进入这个getProxy,return Proxy.newProxyInstance(this.determineClassLoader(classLoader), this.cache.proxiedInterfaces, this);

使用到了newProxyInstance来创建代理对象,表示是JDK内部动态代理的实现,因为JDK动态代理使用Java.lang.reflect.Proxy类中的newProxyInstance方法进行创建键。而如果是CGLIB动态代理的话,会获取到Enhance进行增强.

无论是哪种动态代理,都是在目标类所属的bean在初始化时使用的beanPostProcessors进行代理对象生成,通过@EnableAspectJAutoProxy的属性值来决定是那种动态代理。

Spring AOP的exposeProxy机制

现在创建一个update方法,使用@Before("execution(* com.example.service.impl.UserServiceImpl.*(..))"),对之前的add和update进行增强,得到

但如果是在add方法中调用update()方法,结果是update()方法并没有进行增强,表示现在的update()方法不是通过代理对象进行调用的,而是当前update()方法所属类的对象。如果要是代理对象去取,可以让UserServiceImpl实现ApplicationContextAware接口获取当前Spring应用的上下文,然后从上下文中手动获取代理对象。

java 复制代码
package com.example.service.impl;

import com.example.service.UserService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserServiceImpl implements UserService, ApplicationContextAware {
    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    @Override
    public void add() {
        System.out.println("add方法实现了");
        UserService proxy = (UserService) context.getBean("userService");
        proxy.update();
    }

    @Override
    public void update() {
        System.out.println("update方法实现了");
    }

}

现在实现了ApplicationContextAware接口后,从哪里获取当前spring的ioc容器?

是在bean的初始化时吗?AbstractAutowireCapableBeanFactory.class类中有个doCreateBean方法,此方法中初始化bean打个断点

进入init方法, 其中 ApplicationContextAware接口中的setApplicationContext在invokeAwareMethods这里执行的吗?

java 复制代码
private void invokeAwareMethods(String beanName, Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                BeanNameAware beanNameAware = (BeanNameAware)bean;
                beanNameAware.setBeanName(beanName);
            }

            if (bean instanceof BeanClassLoaderAware) {
                BeanClassLoaderAware beanClassLoaderAware = (BeanClassLoaderAware)bean;
                ClassLoader bcl = this.getBeanClassLoader();
                if (bcl != null) {
                    beanClassLoaderAware.setBeanClassLoader(bcl);
                }
            }

            if (bean instanceof BeanFactoryAware) {
                BeanFactoryAware beanFactoryAware = (BeanFactoryAware)bean;
                beanFactoryAware.setBeanFactory(this);
            }
        }

    }

由于 ApplicationContextAware不属于上面的接口,因此不在invokeAwareMethods这里执行。本方法(initializeBean)中applyBeanPostProcessorsBeforeInitialization方法,执行postProcessBeforeInitialization,

java 复制代码
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Aware) {
            this.invokeAwareInterfaces(bean);
        }

        return bean;
    }

总结: Application容器的获取是依赖于ApplicationContextAwareProcessor来实现的,ApplicationContextAwareProcessor中的setApplicationContext是在bean初始化时调用applyBeanPostProcessorsAfterInitialization这个方法执行的。

还有没有其他方法呢?有的。

复制代码
@EnableAspectJAutoProxy(exposeProxy = true)

exposeProxy是否将代理对象和ThreadLocal进行绑定,true为绑定

java 复制代码
package com.example.service.impl;

import com.example.service.UserService;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserServiceImpl implements UserService{

    @Override
    public void add() {
        System.out.println("add方法实现了");
        // 通过AopContext这个工具类获取ThreadLocal中的代理对象
        UserService proxy = (UserService) AopContext.currentProxy();
        proxy.update();
    }
    @Override
    public void update() {
        System.out.println("update方法实现了");
    }

}

AopContext这个工具类获取ThreadLocal中的代理对象是真的吗?是的

那么是什么时候将代理对象和ThreadLocal进行绑定?以jdk动态代理为例子。在JdkDynamicAopProxy.class中的invoke方法中,有,AopContext.setCurrentProxy(proxy)实际上是在背后使用了一个ThreadLocal变量来存储当前代理对象。这样,在同一个线程中的任何地方,都可以通过AopContext.currentProxy()方法获取到当前的代理对象

复制代码
if (this.advised.exposeProxy) {
    oldProxy = AopContext.setCurrentProxy(proxy);
    setProxyContext = true;
}

机制主要用于解决内部方法调用无法应用切面逻辑的问题。通过暴露代理对象并手动使用它,可以在必要时绕过这个限制

相关推荐
猿六凯2 分钟前
历年云南大学计算机复试上机真题
java·华为od·华为
尽力不摆烂的阿方6 分钟前
《图解设计模式》 学习笔记
java·笔记·学习·设计模式
__Benco1 小时前
OpenHarmony子系统开发 - Rust编译构建指导
开发语言·人工智能·后端·rust·harmonyos
穆骊瑶1 小时前
Python语言的代码重构
开发语言·后端·golang
Aska_Lv1 小时前
Redis---配置文件详解
后端
张胤尘1 小时前
【十五】Golang 结构体
开发语言·后端·golang
Aska_Lv1 小时前
Redis---RDB_AOF_混合持久化
后端
Java韩立1 小时前
基于Spring Boot的航司互售系统
java·spring boot·后端
翱翔-蓝天2 小时前
Go 语言 `map` 详解
开发语言·后端·golang
东阳马生架构2 小时前
Netty基础—4.NIO的使用简介二
java·网络·netty