Spring5系列(十) | 动态代理底层实现

2021-12-13 09:43:45 浏览数 (2)

我们在上面的几篇文章中已经了解了如何使用spring进行aop的开发,本篇文章我们来介绍一下动态代理的底层实现。

一. AOP编程概念

代码语言:javascript复制
AOP: Aspect oriented programming: 面向切面编程 = spring动态代理开发。以切面为基本单位的程序开发,通过切面间的批次协同,相互调用,完成程序构建。  切面 = 切入点   额外功能

OOP: Object Oriented Programming: 面向对象编程,以对象为基本单位的程序开发,通过过程间的批次协同,相互调用,完成程序构建

POP: Procedure Oriented Programming: 面向过程(函数,方法)编程,以过程为基本单位的程序开发,通过过程间彼此协同,相互调用,完成程序构建
复制代码
代码语言:javascript复制
AOP编程的本质就是Spring动态代理开发,通过代理类为原始类增加额外功能。好处就是利于原始类的维护

注意: aop编程不可能取代oop功能,是一种有力的补充。
复制代码

回顾AOP编程的开发步骤:

  • 原始对象
  • 额外功能(MethodBeforeAdvice, MethodInterceptor)
  • 切入点(切入点表达式)
  • 组装
代码语言:javascript复制
切面 = 切入点   额外功能
复制代码

核心问题:

代码语言:javascript复制
1. AOP如何创建代理类:
  通过动态字节码技术
  
2. Spring工厂如何加工创建代理对象
  通过原始对象的id, 获得的是代理对象
复制代码

二. 动态代理实现

spring底层的动态代理有两种实现方式,一是JDK的动态代理技术,而是Cglib开源框架提供的动态代理技术。

2.1 JDK动态代理

jdk的动态代理,必须是基于接口进行代理,也就是我们的目标类必须实现一个接口,才能进行代理。我们给出案例,这里省略了UserService, 和 UserServiceImpl.

代码语言:javascript复制
创建代理三个要素: 
  1. 原始对象
  2. 额外功能
  3. 代理对象和原始对象实现相同的接口
        
JDK为我们了提供了Proxy.newInstance(ClassLoader var0, Class<?>[] var1, InvocationHandler var2) 方法类实现动态代理技术。

参数介绍  
@param: ClassLoader var0: 创建代理对象所需的类加载器
@param: interfaces: 和原始对象实现的接口数组
@param: InvocationHandler: 额外功能

这里InvocatioHandler也是一个接口,所以我们需要传如一个实现。该接口有一个方法需要实现:  Object invoke(Object proxy, Method method, Object[] args);

@param Object proxy:代表代理对象,忽略,不要使用
@param Methdod method: 代表额外功能增加给的原始方法
@param Object[] args: 原始方法的参数
@return: Object: 原始方法的返回值

这种写法和我们之前的MethodInterceptor很像,毕竟底层就是这么实现的。我们直接程序应该怎么写
复制代码
代码语言:javascript复制
public class JDKProxyTest{

  public static void main(String[] args){
    // 1. 创建原始对象
    Userservice userService = new UserServiceImpl();
    
    // 2. 创建动态代理类
      UserService proxy = (UserService)Proxy.newInstance(userService.getClass.getClassLoader() ,userService.getClass.getInterfaces() , new InvocationHandler(){
      
      public Object invoke(Object proxy, Method method, Object[]args){
        
        // 前置额外功能
       	Object obj =  method.invoke(userService, args);
        // 后置额外功能
        return obj;
      }
    });

    proxy.login("abc", "123456");

  }
}
复制代码

我们在InvocationHandler接口中的实现方法里加入了我们想要的额外功能,并通过调用method.invoke() 完成原始方法的调用。同时将原始方法的返回值返回,此时我们需要使用接口来接收代理对象(因为代理对象和原始对象实现了同一个接口),要注意的是这个代理对象是动态生成的,所以我们可能找不到他的具体源码,当我们使用他调用方法的时候,额外功能就可以执行了。

注意事项:

代码语言:javascript复制
类加载器的作用:
1. 通过类加载器吧对应的类字节码文件加载到JVM中
2. 通过类加载器创建类的Class对象进而创建这个类的对象

如何获得类加载器:
虚拟机为每一个类的.class文件自动分配与之对应的ClassLoader,动态代理类没有源文件,它是通过动态字节码技术生成,把字节码直接写入jvm.
此时在动态代创建的过程中,需要ClassLoader创建代理类的Class对象,可是因为动态代理类没有.classs文件,JVM也就不会为他分配ClassLoader,但是又需要,就只能借用一个。所以找一个我们自己写的类获取Class对象在调用getClassLoader()即可,这里注意不要使用JDK的类获取,因为不是一个类加载器。
复制代码

2.2 CgLib动态代理

上面说了JDK的动态代理技术的实现。但是JDK的动态代理技术有一个弊端,就是原始类必须要实现一个接口,如果原始类没有实现任何接口,此时想要给他创建动态代理类,JDK的动态代理就实现不了了。而Cglib可以实现。

代码语言:javascript复制
cglib动态代理原理:
  cglib所创建的代理类是通过继承的方式实现的,他会继承原始类。原始类作为父类,代理类作为子类,这样就可以保证二者方法相同。而不需要实现接口。
复制代码

代码实现:

代码语言:javascript复制
public class UserService{
  
  public boolean login(String name, String password){
    	System.out.println("login...");
  }
  
  public void register(User user){
     System.out.println("register...");
  }
  
}


// 测试类
class Test{
  /**
  	Enhancer.setClassLoader();
  
  */
  public static void main(String[] args){
    	UserService userService = new UserService();
    	Enhancer enhancer = new Enhancer();
    	enhander.setClassLoader(Test.class.getClassLoader());
    	enhander.setSuperClass(userService.class);
    	MethodInteceptor interceptor = new MethodInterceptor(){
        @Override
        public Object(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable{
          	
          	System.out.println("----log-----");
          	Object obj = method.invoke(userService, args);
          	return obj;
         	 
        }
      };
    	enhancer.setCallBack(interceptor);
    	UserService proxy = (UserService)Enhancer.create();
    	proxy.login("abc", "123");
   	 	proxy.register(new User());
  }
}

复制代码

总结:

  1. JDK动态代理,Proxy.newInstance : 通过接口创建代理的实现类
  2. CGlib: 动态代理, Enhancer, 通过父子类继承

三. spring工厂如何创建代理对象

上面我们讲述了spring中两种动态代理的实现。通过动态代理技术,就可以创建出代理对象。那么在spring中,为什么我们通过原始bean的id就可以得到代理对象呢。我们来浅析一下他的原理。其实他主要还是通过BeanPostProcessor这个接口实现的。这个接口我们在前面介绍过。

相当于我们在加工的过程中,创建了他的动态代理对象,进行了返回。这样我们获取的对象就是动态代理对象了。我们来写个案例。

代码语言:javascript复制
public interface UserService{
  
  void register(User user);
  
  boolean login(String name, String password)
  
}

public class UserServiceImpl implements UserService{
  @Override
  public void register(User user){
    System.out.println("registe----")
  }
  
  @Override
  public boolean login(String name, String password){
    System.out.println("login-----")
  }
}

public class ProxyBeanPostProcessor implements BeanPostProcessor{
  
  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{
    return bean;
  }
  
   @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{
    return Proxy.newInstance(this.getClass.getClassLoader(), bean.getClass.getInterfaces,new InvocationHandler(){
      public Object invoke(Object proxy, Method method, Object[] args){
        System.out.println("---log advice ---");
        Object ret = method.invoke(bean, args);
        
        return ret;
      }
      
    });
  }
  
}
复制代码
代码语言:javascript复制
<bean id="userService" class="com.xxx.UserServiceImpl" />
<bean id="proxy" class="com.xxx.ProxyBeanPostProcessor" />
复制代码

而在spring的源码中,是通过一个叫做AbstractAutoProxyCreator,这个类就是专门用来创建代理对象的,而他的本质就是实现了BeanPostProcessor接口,在方法中创建了代理对象。

好了关于动态代理的一些概念和底层实现我们就先介绍到这里.

0 人点赞