深圳幻海软件技术有限公司 欢迎您!

Spring框架之Spring容器扩展

2023-02-28

通常情况下,应用程序开发人员不需要对ApplicationContext实现类进行子类化。相反,SpringIoC容器可以通过插入特殊集成接口的实现来进行扩展。接下来的几节将描述这些集成接口。通过使用BeanPostProcessor来定制BeanBeanPostProcessor接口定义了回调方法

通常情况下,应用程序开发人员不需要对ApplicationContext实现类进行子类化。相反,SpringIoC容器可以通过插入特殊集成接口的实现来进行扩展。接下来的几节将描述这些集成接口。

通过使用BeanPostProcessor来定制Bean

BeanPostProcessor接口定义了回调方法,你可以实现这些方法来提供你自己的(或覆盖容器的默认)实例化逻辑、依赖性解析逻辑等等。如果你想在Spring容器完成实例化、配置和初始化Bean之后实现一些自定义逻辑,你可以插入一个或多个自定义BeanPostProcessor实现。

你可以配置多个BeanPostProcessor实例,你可以通过设置order属性控制这些BeanPostProcessor实例的运行顺序。只有当BeanPostProcessor实现了Ordered接口时,你才能设置这个属性。如果你编写自己的BeanPostProcessor,你也应该考虑实现Ordered接口。更多细节,请参见BeanPostProcessor和Ordered接口的javadoc。也请看关于BeanPostProcessor实例的程序化注册的说明。

BeanPostProcessor实例对Bean实例进行操作。也就是说,SpringIoC容器实例化一个Bean实例,然后由BeanPostProcessor实例进行工作。BeanPostProcessor实例在每个容器中都有作用域。这只有在你使用容器层次结构时才有意义。如果你在一个容器中定义了一个BeanPostProcessor,它只对该容器中的Bean进行后处理。换句话说,在一个容器中定义的Bean不会被另一个容器中定义的BeanPostProcessor进行后处理,即使这两个容器是同一层次结构的一部分。

ApplicationContext会自动检测配置元数据中定义了实现BeanPostProcessor接口的Bean。ApplicationContext将这些 bean 注册为后处理器,以便以后在创建 bean 时可以调用它们。Bean后处理器可以像其他Bean一样被部署在容器中。

请注意,当通过在配置类上使用 @Bean 工厂方法来声明 BeanPostProcessor 时,工厂方法的返回类型应该是实现类本身或至少是org.springframework.beans.factory.config.BeanPostProcessor 接口,明确指出该 Bean的后处理器性质。否则,ApplicationContext无法在完全创建它之前按类型自动检测它。由于BeanPostProcessor需要尽早被实例化,以便应用于上下文中其它Bean的初始化,所以这种早期的类型检测是至关重要的。

下面的例子展示了如何在ApplicationContext的上下文中编写、注册和使用BeanPostProcessors。

public class HelloWorld {
   private String message;
   public void setMessage(String message){
      this.message  = message;
   }
   public void getMessage(){
      System.out.println("Your Message : " + message);
   }
   public void init(){
      System.out.println("Bean is going through init.");
   }
   public void destroy(){
      System.out.println("Bean will destroy now.");
   }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
public class InitHelloWorld implements BeanPostProcessor {
   public Object postProcessBeforeInitialization(Object bean, String beanName) 
      throws BeansException {      
      System.out.println("BeforeInitialization : " + beanName);
      return bean;  // you can return any other object as well
   }
   public Object postProcessAfterInitialization(Object bean, String beanName) 
      throws BeansException {
      
      System.out.println("AfterInitialization : " + beanName);
      return bean;  // you can return any other object as well
   }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

以下是MainApp.java文件的内容。这里你需要注册一个shutdown钩子 registerShutdownHook()方法,该方法在AbstractApplicationContext类上声明。这将确保一个优雅的shutdown,并调用相关的销毁方法。

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
   public static void main(String[] args) {
      AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
      HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
      obj.getMessage();
      context.registerShutdownHook();
   }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
   <bean id = "helloWorld" class = "com.tutorialspoint.HelloWorld"
      init-method = "init" destroy-method = "destroy">
      <property name = "message" value = "Hello World!"/>
   </bean>
   <bean class = "com.tutorialspoint.InitHelloWorld" />
</beans>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

Output:

BeforeInitialization : helloWorld
Bean is going through init.
AfterInitialization : helloWorld
Your Message : Hello World!
Bean will destroy now.
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

BeanFactoryPostProcessor的实现被用来读取配置元数据,并在IOC容器实例化Bean之前对其进行修改。我们可以配置多个BeanFactoryPostProcessor,你还可以通过设置order属性来控制这些BeanFactoryPostProcessor的执行顺序。只有当BeanFactoryPostProcessor实现了Ordered接口时,你才能设置order属性。

BeanFactoryPostProcessor是一个功能接口,它有一个抽象方法postProcessBeanFactory(),实现这个方法,可以自定义修改Bean定义。请注意,当这个方法被调用时,所有的Bean定义都已经被加载,但还没有Bean被实例化。它允许重写或添加属性,即使是对急于初始化的Bean。BeanFactoryPostProcessor定义如下:

@FunctionalInterface

@FunctionalInterface
public interface BeanFactoryPostProcessor {
  void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
    throws BeansException;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

BeanFactoryPostProcessor示例

举个简单的例子,假如我们已经在properties配置文件中为数据库配置设置了属性,但在运行时我们想更改url,其它属性保持不变,我们可以用BeanFactoryPostProcessor访问bean定义,并修改属性值。当然,我们可以配置多个properties文件,并进行切换,这里仅做一个示例。

  • db.properties
db.driverClassName=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/netjs
db.username=root
db.password=admin
pool.initialSize=5
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • datasource定义
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
  <property name="driverClassName" value = "${db.driverClassName}" />
  <property name="url" value = "${db.url}" />
  <property name="username" value = "${db.username}" />
  <property name="password" value = "${db.password}" />
  <property name="initialSize" value = "${pool.initialSize}" /
</bean>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • BeanFactoryPostProcessor实现类
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Ordered;
public class TestDBPostProcessor implements BeanFactoryPostProcessor, Ordered {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
      throws BeansException {
    System.out.println("In postProcessBeanFactory");
    // Getting the dataSource bean
    BeanDefinition bd = beanFactory.getBeanDefinition("dataSource");
    if(bd.hasPropertyValues()){
      MutablePropertyValues pvs = bd.getPropertyValues();
      PropertyValue[] pvArray = pvs.getPropertyValues();
      for (PropertyValue pv : pvArray) {
        System.out.println("pv -- " + pv.getName());
        // changing value for url property
        if(pv.getName().equals("url")){
          pvs.add(pv.getName(), "jdbc:mysql://localhost:3306/TestSchema");
        }
      }
    } 
  }
  @Override
  public int getOrder() {
    // TODO Auto-generated method stub
    return 0;
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 在配置文件中注册BeanFactoryPostProcessor
<bean class="com.demo.config.TestDBPostProcessor"  />
  • 1.
public List<Employee> findAllEmployees() {
  System.out.println("URL " + ((BasicDataSource)jdbcTemplate.getDataSource()).getUrl());
  return this.jdbcTemplate.query(SELECT_ALL_QUERY, (ResultSet rs) -> {
    List<Employee> list = new ArrayList<Employee>();  
    while(rs.next()){
      Employee emp = new Employee();
      emp.setEmpId(rs.getInt("id"));
      emp.setEmpName(rs.getString("name"));
      emp.setAge(rs.getInt("age"));
      list.add(emp);
    }
    return list;
  });
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
public class App {
  public static void main(String[] args) {      
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext
     ("appcontext.xml");
    EmployeeDAO dao = (EmployeeDAO)context.getBean("employeeDAOImpl");  
    List<Employee> empList = dao.findAllEmployees();
    for(Employee emp : empList){
      System.out.println("Name - "+ emp.getEmpName() + " Age - " 
      + emp.getAge());
    }
    context.close();    
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

Output:

In postProcessBeanFactory
pv -- driverClassName
pv -- url
pv -- username
pv -- password
pvinitialSize

URL jdbc:mysql://localhost:3306/TestSchema
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

FactoryBean

在Spring的bean容器中,有两种bean:普通bean和工厂bean。Spring直接使用前者,而后者可以自己产生对象,由框架来管理。简单地说,我们可以通过实现org.springframework.beans.factory.FactoryBean接口来建立一个工厂bean。

FactoryBean接口提供了三种方法:

  • T getObject():返回该工厂创建对象的一个实例。该实例可能会被共享,这取决于该工厂是返回单体还是原型Bean。
  • boolean isSingleton():如果这个FactoryBean返回单体,则返回true,否则返回false。这个方法的默认实现会返回true。
  • Class getObjectType():返回由getObject()方法返回的对象类型,如果事先不知道该类型,则返回null。

示例:

public class Tool {

    private int id;

    // standard constructors, getters and setters
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
public class ToolFactory implements FactoryBean<Tool> {
    private int factoryId;
    private int toolId;
    @Override
    public Tool getObject() throws Exception {
        return new Tool(toolId);
    }
    @Override
    public Class<?> getObjectType() {
        return Tool.class;
    }
    @Override
    public boolean isSingleton() {
        return false;
    }
    // standard setters and getters
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
@Configuration
public class FactoryBeanAppConfig { 
    @Bean(name = "tool")
    public ToolFactory toolFactory() {
        ToolFactory factory = new ToolFactory();
        factory.setFactoryId(7070);
        factory.setToolId(2);
        return factory;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FactoryBeanAppConfig.class);
        Tool tool = context.getBean("tool", Tool.class);
        System.out.println(tool);
        ToolFactory toolFactory = context.getBean("&tool",ToolFactory.class);
        System.out.println(toolFactory);
        context.close();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

当我们要获取FactoryBean实例时,需要在bean的id前面加上&符号,比如getBean('&tool')。