当前位置: 首页 > article >正文

【手写一个spring】spring源码的简单实现--BeanPostProcessor(实现AOP)以及JDK动态代理/CGLIB动态代理

文章目录

  • `BeanPostProcessor`概念
  • `BeanPostProcessor`接口中的两个方法
  • `BeanPostProcessor`的实例化与注册
  • `BeanPostProcessor`的执行时机
  • 实现`AOP`
    • 动态代理技术
      • `JDK`动态代理:
      • `CGLIB`动态代理:
    • `JDK`动态代理和`CGLIB`动态代理的区别
      • `JDK`动态代理
      • `CGLIB`动态代理
      • 使用场景
      • 性能差异
      • 总结

BeanPostProcessor概念

BeanPostProcessorSpring框架中一个非常重要的接口,它允许开发者Bean的初始化前后对Bean实例进行自定义的修改和处理.

调用的阶段:在Bean对象初识化的前后

BeanPostProcessor接口中的两个方法

  • postProcessBeforeInitialization(Object bean, String beanName):在Bean初始化方法调用之前执行。该方法允许开发者在Bean的初始化回调之前,对Bean实例进行自定义的处理。
  • postProcessAfterInitialization(Object bean, String beanName):在Bean初始化方法调用之后执行。该方法允许开发者在Bean的初始化回调之后,对Bean实例进行进一步的自定义处理,如代理封装等。

BeanPostProcessor的实例化与注册

Spring容器的启动过程中,Spring会自动检测所有实现了BeanPostProcessor接口的Bean定义,并将它们实例化。这些BeanPostProcessor实例会被注册到Spring容器的BeanFactory中,以便在后续的Bean创建过程中使用。

BeanPostProcessor的执行时机

Spring容器创建了一个Bean实例后,会检查是否有BeanPostProcessor实例需要对该Bean进行处理。
如果有,Spring会先调用所有BeanPostProcessorpostProcessBeforeInitialization方法,允许开发者在Bean初始化之前对Bean进行修改。
接着,Spring会调用Bean的初始化方法(如实现了InitializingBean接口的afterPropertiesSet方法,或者在配置文件中指定的init-method)。
最后,Spring会调用所有BeanPostProcessorpostProcessAfterInitialization方法,允许开发者在Bean初始化之后对Bean进行进一步的修改。

自定义一个BeanPostProcessor接口:

/***
 *
 * bean的后置处理器
 * */
public interface BeanPostProcessor {
    /**
     * 前置
     * */
    public void postProcessBeforeInitializetion(String beanName,Object bean);
    
    /**
     * 后置
     * */
    public Object postProcessAfterInitializetion(String beanName, Object bean);
}

自定义一个类:BailLuBeanPostProcessor 对象实现这个接口:(并且,指定了userService执行接口逻辑)

@Component
public class BailLuBeanPostProcessor implements BeanPostProcessor {
    @Override
    public void postProcessBeforeInitializetion(String beanName, Object bean) {
        if("userService".equals(beanName)){
            System.out.println("bean对象初始化前--------");
        }
    }

    /***
     *
     * 后置处理器
     *
     * */

    public Object postProcessAfterInitializetion(String beanName, Object bean) {
        if("userService".equals(beanName)){
         System.out.println("bean对象初始化后-------");
        return bean;
    }
}

容器中调用该接口的时机:

		/**
       *存放实现了BeanPostProcessor接口的Bean对象
       * */
      private ArrayList<BeanPostProcessor> beanPostProcessorList=new ArrayList<>();
							
	  public BaiLuApplicationContext(Class configClass){
							Class<?> clazz = classLoader.loadClass(className);//传入全限定类名
                            if (clazz.isAnnotationPresent(Component.class)) {
                             
                                if(BeanPostProcessor.class.isAssignableFrom(clazz)){
                                    //说明该类实现了BeanPostProcessor接口
                                    BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
                                    beanPostProcessorList.add(instance);
                                }
      }                          

实现AOP

AOP(面向切面编程)的底层原理主要依赖于动态代理技术。这种技术允许开发者在不修改原有代码的情况下,为主干功能添加新的功能或行为。

动态代理技术

AOP底层主要使用两种动态代理技术:JDK动态代理和CGLIB动态代理

JDK动态代理:

JDK动态代理是基于接口的动态代理技术

Proxy.newProxyInstanceJava 中用于创建动态代理对象的方法。这个方法是 java.lang.reflect.Proxy 类的一部分,它允许你在运行时创建一个实现了指定接口的代理对象这个代理对象会将方法调用转发给一个指定的调用处理器InvocationHandler)。
当调用代理对象的方法时,实际上会调用InvocationHandler接口的invoke方法,从而可以在该方法中编写增强逻辑。
以下是 Proxy.newProxyInstance 方法的签名:

public static Object newProxyInstance(ClassLoader loader,
                                   Class<?>[] interfaces,
                                    InvocationHandler h)

参数:

  • ClassLoader loader:定义代理类的类加载器。通常,你可以使用目标类的类加载器来获取这个参数。
  • Class<?>[] interfaces:代理类要实现的接口列表。代理类将实现这些接口中声明的所有方法
  • InvocationHandler h调用处理器,当代理对象的方法被调用时,这个处理器的 invoke 方法会被调用

以下是JDK动态代理实现AOP:

  1. 存在一个实现了UserInterface接口的实现类:
@Component("userService")
public class UserService implements UserInterface{

    @Autowird
    private OrderService orderService;
    
    public void test(){
        System.out.println("~~~考察AOP切面逻辑~~~");
    }
  1. 在后置处理器中生成代理对象proxyInstance
@Component
public class BailLuBeanPostProcessor implements BeanPostProcessor {
	/***
     *
     * 后置处理器
     *
     * */
    public Object postProcessAfterInitializetion(String beanName, Object bean) {

        if("userService".equals(beanName)){
            /*
             * 这里采用的是 JDK动态代理的方式生成代理对象的
             * @Param: proxyInstance--->JDK生成的代理对象,动态代理对象会去执行对象实现的接口
             * */
            Object proxyInstance = Proxy.newProxyInstance(BailLuBeanPostProcessor.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);//这里是执行的接口中的逻辑
                }
            });
            return proxyInstance;
        }
        return bean;
    }
}    
  • 生成一个代理对象proxyInstance;
  • 使用代理对象proxyInstance调用接口中的方法时,会调用invoke方法的逻辑;
  • invoke方法中,对逻辑进行增强,并且调用invoke,实现对原本bean对象中接口的逻辑

CGLIB动态代理:

基于父类的动态代理技术,可以代理没有实现接口的类
AOP(面向切面编程)使用CGLIB动态代理,主要是通过CGLIB在运行时生成目标类的子类,并在子类中重写目标类的方法以插入额外的逻辑(即横切关注点)

使用Enhancer类来创建代理对象。

通过设置父类(目标类)和回调(MethodInterceptor),当调用代理对象的方法时,会触发回调方法中的intercept方法,从而可以在该方法中编写增强逻辑。

JDK动态代理和CGLIB动态代理的区别

JDK动态代理

  1. 基于接口JDK动态代理要求被代理的类必须实现一个或多个接口。代理对象会实现这些接口,并将方法调用委托给目标对象。
  2. 实现机制使用Java反射机制来生成代理对象代理对象会拦截接口中的方法调用,并通过InvocationHandler.invoke()方法执行额外的逻辑

CGLIB动态代理

  1. 基于类CGLIB动态代理通过生成目标类的子类来实现代理。它不要求目标类必须实现接口,因此适用于没有实现接口的类
  2. 实现机制:使用ASM字节码生成框架来生成目标类的子类,并重写目标类的方法以实现代理逻辑

使用场景

JDK动态代理:适用于接口驱动的编程模式。如果目标类实现了接口,并且希望保持简单的依赖关系,那么JDK动态代理是一个很好的选择。
Spring AOP中,如果目标对象实现了接口,Spring默认使用JDK动态代理。
CGLIB动态代理:适用于目标类没有实现接口但需要代理的场景。例如,一些现有的类或者第三方库的类没有提供接口,此时可以使用CGLIB动态代理。
Spring AOP中,如果目标对象没有实现任何接口,Spring AOP会自动使用CGLIB动态代理。

性能差异

JDK动态代理:对于实现了接口的类来说JDK动态代理在创建代理对象时开销较小,因为它仅依赖反射机制来处理接口方法的调用
对于频繁调用代理方法的场景,JDK动态代理可能比CGLIB略慢,因为每次调用都涉及反射。
CGLIB动态代理:由于CGLIB通过字节码生成来创建代理类,生成代理类的开销比JDK动态代理高一些,尤其是在代理类较多的情况下。
但是,CGLIB代理的实际方法调用性能更高,因为它通过字节码操作减少了反射调用的开销

总结

  1. spring容器会在目标类实现了接口的时候,默认选择JDK动态代理;在目标类没有实现接口的时候,默认选择CGLIB动态代理;
  2. CGLIB是基于字节码生成来创建代理类的,JDK动态代理是基于反射来创建代理类的,JDK生成代理类的开销相较于CGLIB相对较小;
  3. CGLIB代理的实际方法的调用上性能更高,因为他是通过字节码操作减少了反射调用的开销;
  4. CGLIB动态代理需要额外引入CGLIB库依赖,而JDK动态代理是JDK标准库中的一部分,无需引入额外的依赖.
  5. final类和方法:CGLIB动态代理无法代理final类或final方法,因为这些类和方法无法被继承和重写。而JDK动态代理则没有这个问题,因为它只是代理接口中的方法。

http://www.kler.cn/a/407126.html

相关文章:

  • 【Linux】vim的使用
  • electron主进程和渲染进程之间的通信
  • 网络爬虫——爬虫项目案例
  • Spring Security @PreAuthorize注解
  • Docker+Nginx | Docker(Nginx) + Docker(fastapi)反向代理
  • 1、HCIP之RSTP协议与STP相关安全配置
  • 太速科技-297-基于XC7A100T的PCIe千兆电口以太网收发卡
  • css效果
  • 如何进行模板特化和偏特化?
  • 学习日记_20241123_聚类方法(高斯混合模型)续
  • 蚁群算法(Ant Colony Optimization, ACO)
  • 速盾:CDN缓存的工作原理是什么?
  • Linux---ps命令
  • 论文阅读——Performance Evaluation of Passive Tag to Tag Communications(一)
  • (动画)Qt控件 QLCDNumer
  • Python Scikit-learn简介
  • ValueError: bbox_params must be specified for bbox transformations
  • path.resolve、path.join
  • mfc140u.dll是什么文件,mfc140u.dll怎么解决【最新方法】
  • 碳化硅陶瓷膜的最佳使用期限
  • 重生之我在学环境变量
  • 信号signal
  • 【转】std::unique_ptr 删除器的秘密
  • 软件工程复习知识点
  • Mistral推出“Le Chat”,对标ChatGPT
  • pytest日志总结