如何拓展Spring
First of All : 认真阅读过Spring源码就完全不是问题,但记忆总是容易遗忘的。所以记住Spring 启动的过程几个重点即拓展点,反向也可推出Spring 的启动过程。同时拓展的过程也是破坏的过程,在某几个过程中可以使Spring容器不能正常使用。
容器启动时
spring.handlers/spring.schemas
标签解析过程
假如我想自定义一个XML标签,解析出来的内容希望通过 NBNamespaceHandler 来处理,为了满足这个简单的需求,需要准备以下的内容
1.自定义NamespaceHandler,注册自定义标签的解析类
继承NamespaceHandlerSupport,并重写 init() 方法,添加BeanDefinitionParser注册解析器。
2.实现BeanDefinitionParser来解析自定义标签
用作解析标签的属性和内容,因为是Spring的拓展,所以解析出来的内容往往就是一个个BeanDefinition。
3.配置spring.handlers 和 spring.schemas
http\://example.com/schema/my-extension=com.example.namespace.MyNamespaceHandler
http://example.com/schema/my-extension 这是一个虚构的URL,代表自定义的XML命名空间。在XML中通过xmlns前缀引用这个命名空间。同时还可以定义命名空间的别名,别名不是必须。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://example.com/schema/my-extension"
xsi:schemaLocation="...">
<!-- 直接使用完整命名空间URI -->
<http://example.com/schema/my-extension:NB someAttribute="value" />
<!-- 其他Spring beans配置... -->
</beans>
spring.schemas 定义XML命名空间的XSD文件位置,
http\://example.com/schema/my-extension.xsd=classpath:/schemas/my-extension.xsd
同样http://example.com/schema/my-extension.xsd 可以是虚拟或实际的地址,使用时直接从本地去就获取XSD,而不用真的去互联网下载。即spring.schemas提供一个映射方式,将自定义命名空间URI和本地路径下的XSD文件相对路径对应起来。。
Q : XSD是什么?
A : XSD 定义了XML中元素的结构、类型、约束规则。是XML文档能够更好的表述结构,还能校验数据有效性。
Q : XSD 和Parser的关系
A : Parser是用来读取和解析XML的组件。虽然解析器可以独立解析XML,但是XSD可以确保XML符合预定义的规范和约束,提高了数据质量与一致性。
BeanDefinitionRegistry
BeanDefinietionRegistry是Spring 中的一个重要接口,在Spring IOC容器启动过程中,BeanDefinitionRegistry 负责接收、存储和管理所有待创建的BeanDefinition。当解析器将配置信息(XML、注解、其它配置方式)转换成BeanDefinition后,会将这些BeanDefinition注册到BeanDefinitionRegistry中。
扩展点:实现BeanDefinitionRegistryPostProcessor 接口的类能够在BeanDefinition定义被注册到BeanDefinitionRegistry 后立即执行自定义逻辑,这个阶段可以用来动态修改BeanDefiniton。
- ConfigurationClassPostProcessor
处理@Configuration注解的类以及其中的@Bean方法,将他们转换为Spring可以理解的BeanDefinition。
- ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar没有直接实现BeanDefinitionRegistryPostProcessor,但通常与@Import注解配合使用,在运行时像容器动态注册额外的bean定义。
- 自定义
public class DestoryAllBeansProcessor implements BeanDefinitionRegistryPostProcessor{
@Override
public void postPorcessBeanDefinitionRegistry(BeanDefinitionRegistry registry){
String[] beanDefinitionNames = registry.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
registry.removeBeanDefinition(beanName);
System.out.println("Bean [" + beanName + "] has been removed from the BeanDefinitionRegistry.");
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// This method is not used in this case as we are only interested in the registry. BeanFactory准备就绪后,BeanDefinition都已经加载实例化前执行。
}
}
上面的postProcessBeanDefinitionRegistry方法是在所有BeanDefinition注册进BeanDefinitionRegistry后执行的,registry.removeBeanDefinition(beanName); 就这么轻轻松松的把所有的bean都给删了,这会导致容器无法初始化任何bean。
BeanFactory
BeanFactory是Spring 框架的核心接口,是IOC容器的基础。BeanFactory 提供了一系列关于Bean的生命周期管理和依赖注入的服务。
整个执行过程可以分为
- 初始化BeanFactory
- 调用BeanFactoryPostPorcessor
- 实例化Bean
- 调用BeanPostPorcessor
- 初始化Bean
BeanFactory
- DefaultListableBeanFactory
默认Beanfactory的实现,不仅仅实现了BeanFactory接口,还实现了ListableBeanFactory,可以查询容器中所有已注册的Bean定义。
- AnnotationConfigApplicationContext
可以直接注册@Configuration注解的Java类,然后通过类级别的注解和方法级别的注解来定义Bean
- GenericApplicationContext
GenericApplicationContext包含了一个DefaultListableBeanFactory实例,因此继承了ListableBeanFactory的所有功能。
- 自定义BeanFactory
建议基于继承DefaultListableBeanFactory或GenericApplicationContext。
BeanFactoryPostProcessor
BeanFactoryPostProcessor 的执行是在Bean注册进BeanFactory后,实例化Bean之前,对BeanDefinition进行处理。
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
// 循环执行
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
不同的BeanFactory之间可能存在着依赖关系,需要基于前面处理器处理过的BeanDefinition来执行自身的逻辑处理过的BeanDefinition,所以执行分成多次
执行顺序
- PriorityOrdered
- Ordered/Order
- 其它
这里也同样可以进行破坏。。。来一个干一个。
实例:AtomikosDependsOnBeanFactoryPostProcessor ,Atomikos是一个支持分布式事务处理的Java事务管理器,
@Configuration
public class AtomikosDependsOnBeanFactoryPostProcessor implements BeanFactoryPostProcessor,Ordered {
/**
* 在BeanFactory后处理期间调用,用于向Spring BeanFactory添加事务管理器依赖和消息驱动容器依赖。
*
* @param beanFactory 允许配置的可列表Bean工厂,用于检索和操作Beans。
* @throws BeansException 如果在处理BeanFactory时发生错误。
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 获取所有UserTransactionManager类型的bean名称
String[] transactionManagers = beanFactory.getBeanNamesForType(UserTransactionManager.class, true, false);
for (String transactionManager : transactionManagers) {
// 为每个找到的事务管理器添加依赖
addTransactionManagerDependencies(beanFactory, transactionManager);
}
// 添加消息驱动容器依赖,使用之前找到的所有事务管理器
addMessageDrivenContainerDependencies(beanFactory, transactionManagers);
}
旨在实例化Bean之前 对BeanDefinition进行修改
BeanPostProcessor
到这一步Bean已经创建好了,经历实例化和初始化后的Bean已经算是可以直接使用的了,其中AOP代理已经完成了,但AOP相关放到下节再讲。
BeanPostProcessor接口有两个方法(默认实现都是return bean)
preProcessBeforeInitialization
Spring bean实例化后 初始化前执行,可以修改Bean实例的属性、添加代理
postProcessAfterInitialization
Spring bean初始化后执行,执行后续操作
示例:针对所有的service进行,且业务对此并不需要感知。
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Service) {
// 假设所有服务类都继承自一个名为Service的接口或抽象类
// 根据实际情况做修改
bean.setSomething();
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Service) {
bean.setSomething();
}
return bean;
}
}
AOP
AOP的知识点有很多,什么代理、Advice、切面、三级缓存(常见面试题)等等,甚至还可以延伸到事务@Transactional。
Bean是如何被代理的
1.在创建Bean之前,Spring 首先会检测这个Bean是否有相关的切面配置,通过@Aspect注解或其它方式声明。
2.如果检测到目标Bean有切面配置,Spring将根据代理策略创建一个代理对象
3.Spring容器实际管理的事代理对象,而不是原始的目标Bean,当其他Bean依赖于目标Bean时,注入到依赖者的实际上是经过AOP增强后的代理对象。
4.使用的时候其实已经被换成了代理对象。
首先原始的目标bean依旧存在,代理对象是在运行时生成的,持有对原始目标Bean的引用。
Bean是什么时候被代理的呢?
当Bean A在进行初始化时,发现依赖的Bean需要被代理,那时候就会从三级缓存sigletionFacties中取出代理的工厂方法,完成代理替换。再接着完成Bean A的初始化。
Advice
通知。首先Advice是一个接口,是所有通知类型的顶层接口。
例如AfterReturningAdvice 实现了Adice接口,表示在方法正常返回后通知。当目标方法成功执行并返回后,代理对象会在控制权回到调用之前调用AfterReturningAdvice的afterReturning()方法,如果有对应的切入方法则会执行对应的代码。
多重代理
看下一段伪代码
// 假设我们有一个OrderService接口和其实现类
public interface OrderService {
void placeOrder(Order order);
// 其他方法...
}
public class OrderServiceImpl implements OrderService {
// 实现placeOrder和其他方法...
}
// Spring AOP动态生成的JDK Proxy示例(伪代码)
class EnhancedOrderServiceProxy implements InvocationHandler {
//原始bean
private final OrderService target;
private final List<Advisor> advisors;
public EnhancedOrderServiceProxy(OrderService target, List<Advisor> advisors) {
this.target = target;
this.advisors = advisors;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation = new ReflectiveMethodInvocation(target, method, args, null);
//就这么循环
for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor && ((PointcutAdvisor) advisor).getPointcut().matches(method, target.getClass(), this)) {
advisor.getAdvice().apply(invocation);
}
}
// Proceed with the actual method call
return invocation.proceed();
}
// ...其他辅助方法和逻辑...
}
这也只是一个伪代码,真实的代理对象会更加复杂(更难理解),
得出结论:多重代理不是问题,只需要生成一个代理类,记录完整的代理链路即可。
使用阶段
lookup-method
定义于代码,修改在BeanDefinition,每当执行到配置了lookup-method方法时,Spring容器会介入进行替换,在容器中查找并返回对应的bean。
这种方式通常用来解决单例bean需要引用原型bean的方法,因为代理bean内部的普通成员变量注入无法实现每次调用都会返回新的原型bean示例。
replace-method
使用replace-method方法返回的不是一个新的bean,而是直接替换掉原来的方法,即调用到被替换的方法时,实际执行的是定义的新方法逻辑。
解决的是不修改原有类代码的情况下改变某个方法的行为。
结语
Spring 还有非常多的拓展方式,如:factoryBean、supplier、factoryMethod、等等等。