【手写一个spring】spring源码的简单实现--BeanPostProcessor(实现AOP)以及JDK动态代理/CGLIB动态代理
文章目录
- `BeanPostProcessor`概念
- `BeanPostProcessor`接口中的两个方法
- `BeanPostProcessor`的实例化与注册
- `BeanPostProcessor`的执行时机
- 实现`AOP`
- 动态代理技术
- `JDK`动态代理:
- `CGLIB`动态代理:
- `JDK`动态代理和`CGLIB`动态代理的区别
- `JDK`动态代理
- `CGLIB`动态代理
- 使用场景
- 性能差异
- 总结
BeanPostProcessor
概念
BeanPostProcessor
是Spring
框架中一个非常重要的接口,它允许开发者在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
会先调用所有BeanPostProcessor
的postProcessBeforeInitialization
方法,允许开发者在Bean
初始化之前对Bean
进行修改。
接着,Spring
会调用Bean
的初始化方法(如实现了InitializingBean
接口的afterPropertiesSet
方法,或者在配置文件中指定的init-method
)。
最后,Spring
会调用所有BeanPostProcessor
的postProcessAfterInitialization
方法,允许开发者在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.newProxyInstance
是Java
中用于创建动态代理对象的方法。这个方法是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
:
- 存在一个实现了
UserInterface
接口的实现类:
@Component("userService")
public class UserService implements UserInterface{
@Autowird
private OrderService orderService;
public void test(){
System.out.println("~~~考察AOP切面逻辑~~~");
}
- 在后置处理器中生成代理对象
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
动态代理
- 基于接口:
JDK
动态代理要求被代理的类必须实现一个或多个接口。代理对象会实现这些接口,并将方法调用委托给目标对象。 - 实现机制:使用
Java
反射机制来生成代理对象。代理对象会拦截接口中的方法调用,并通过InvocationHandler.invoke()
方法执行额外的逻辑。
CGLIB
动态代理
- 基于类:
CGLIB
动态代理通过生成目标类的子类来实现代理。它不要求目标类必须实现接口,因此适用于没有实现接口的类。 - 实现机制:使用
ASM
字节码生成框架来生成目标类的子类,并重写目标类的方法以实现代理逻辑。
使用场景
JDK
动态代理:适用于接口驱动的编程模式。如果目标类实现了接口,并且希望保持简单的依赖关系,那么JDK
动态代理是一个很好的选择。
在Spring AOP
中,如果目标对象实现了接口,Spring
默认使用JDK
动态代理。
CGLIB
动态代理:适用于目标类没有实现接口但需要代理的场景。例如,一些现有的类或者第三方库的类没有提供接口,此时可以使用CGLIB
动态代理。
在Spring AOP
中,如果目标对象没有实现任何接口,Spring AOP
会自动使用CGLIB
动态代理。
性能差异
JDK
动态代理:对于实现了接口的类来说,JDK
动态代理在创建代理对象时开销较小,因为它仅依赖反射机制来处理接口方法的调用。
对于频繁调用代理方法的场景,JDK
动态代理可能比CGLIB
略慢,因为每次调用都涉及反射。
CGLIB
动态代理:由于CGLIB
是通过字节码生成来创建代理类,生成代理类的开销比JDK
动态代理高一些,尤其是在代理类较多的情况下。
但是,CGLIB
代理的实际方法调用性能更高,因为它通过字节码操作减少了反射调用的开销。
总结
spring
容器会在目标类实现了接口的时候,默认选择JDK
动态代理;在目标类没有实现接口的时候,默认选择CGLIB
动态代理;CGLIB
是基于字节码生成来创建代理类的,JDK
动态代理是基于反射来创建代理类的,JDK
生成代理类的开销相较于CGLIB
相对较小;CGLIB
代理的实际方法的调用上性能更高,因为他是通过字节码操作减少了反射调用的开销;CGLIB
动态代理需要额外引入CGLIB
库依赖,而JDK
动态代理是JDK
标准库中的一部分,无需引入额外的依赖.final
类和方法:CGLIB
动态代理无法代理final
类或final
方法,因为这些类和方法无法被继承和重写。而JDK
动态代理则没有这个问题,因为它只是代理接口中的方法。