Spring拓展之路

如何拓展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、等等等。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值