1.基于XML配置的扩展
从Spring2.0开始,Spring提供了XML Schema可扩展机制,用户可以自定义XML Schema文件,并自定义XML Bean解析器,并集成到Spring IoC容器中。
不管是bean的定义,还是Spring自身的配置,早期都是通过xml配置完成的。
1.定义schema(xsd)
要支持XML的配置方式,首先需要定义一套XML Schema来描述组件所提供的功能。
2.创建NamespaceHandler(实现NamespaceHandler接口)
除了XMLSchema,我们还需要创建一个自定义的NamespaceHandler来负责解析用户在XML中的配置,
(1)继承NamespaceHandlerSupport
为了简化代码,我们一般都会继承一这个类,然后在init方法中注册处理我们自定义节点的BeanDefinitionParser。
(2)自定义BeanDefinitionParser(实现BeanDefinitionParser接口,可多个)
3.注册Spring Handler和Spring schema
(1)META-INF/spring.handlers
首先需要在META-INF目录下创建一个spring.handlers文件,来配置我们自定义的XML Schema Namespace到我们自定义的NamespaceHandler映射关系。
(2)META-INF/spring.schemas
需要在META-INF目录下创建一个spring.schemas,来配置我们自定义的XML Schema地址到实际jar中classpath映射关系。
例子
(1)目标:
按照上面的步骤,实现如下可扩展XML元素
<myns:dateformat id="dateFormat" pattern="yyyy-MM-dd HH:mm" />
可通过这种方式定义SimpleDateFormat,类似
<bean id="dateFormat" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-HH-dd HH:mm"/>
</bean>
(2)自定义xsd文件
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.lh.com/schema/myns"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.lh.com/schema/myns"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="dateformat">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="pattern" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
其中自定义的命名空间是http://www.lh.com/schema/myns,定义了dateformat元素,属性有pattern。
(3)自定义NamespaceHandler
定义好XML Schema文件,需要定义一个NamespaceHandler解析配置文件。NamespaceHandler接口是非常简单的,只有三个方法。
- init():NamespaceHandler被使用之前调用,完成NamespaceHandler初始化。
- BeanDefinition parse(Element, ParserContext):当遇到顶层元素时被调用
- BeanDefinition decorate(Node, BeanDefinitioinHandler,ParserContext):当遇到一个属性或者嵌套元素的时候调用。
Spring提供了一个默认的实现类NamespaceHandlerSupport,我们只需要在init的时候注册每个元素的解析器即可。
public class DateformatNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("dateformat", new DeteformatDefinitionParser());
}
}
这里实际用到了代理委托的概念,NamespaceHandlerSupport可以注册多个BeanDefinitionParser,NamespaceHandlerSupport负责所有自定义元素的编排,而解析XML的工作委托给各个BeanDefinitionParser负责。
(4)自定义BeanDefinitionParser
如果NamespaceHandler遇到元素类型已经有对应注册的parser,那么此parser将被调用。Spring提供了AbstractsingleBeanDefinitionParser来处理繁重的解析工作,只需要实现两个方法:
- Class<?> getBeanClass(Element):返回元素的Class类型
- void doParser(Element element, BeanDefinitionBuilder builder):添加元素的属性或者构造参数等等。
package com.lh.spring;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.w3c.dom.Element;import java.text.SimpleDateFormat;
public class DateformatDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return SimpleDateFormat.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String pattern = element.getAttribute("pattern");
builder.addConstructorArgValue(pattern);
}
}
(5)注册handler和schema
为了让Spring解析xml的时候能够感知到我们的自定义元素,我们需要把namespaceHandler和xsd文件放到指配置文件中,文件位于META-INF目录中。
- spring.handler文件包含了xml schema uri和Handler类的映射关系,如
http\://www.lh.com/schema/myns=com.lh.spring.DateformatNamespaceHandler
遇到http://www.lh.com/schema/myns命名空间的时候,会交互DateformatNamespaceHandler来处理
- spring.schemas文件包含了xml schema xsd文件命名空间和文件路径的映射关系
http\://www.lh.com/schema/myns.xsd=META-INF/com.lh.date.format/sfm-1.0.xsd
2.基于Java配置的扩展
从Spring3.0开始,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构造bean定义,初始化spring容器。
1.@Import注解
支持Java配置扩展的关键点就是@Import注解,Spring 3.0提供了这个注解用来支持在Configuration类中引入其它的配置类,包括Configuration类,ImportSelector和ImportBeanDefinitionRegistrar的实现类。
我们可以通过这个注解来引入自定义的扩展Bean。
2.自定义注解
这里的关键点是在注解上使用@Import ,从而Spring在处理自定义注解时会实例化并调用@Import引入的类对应的方法。
3.自定义ImportBeanDefinitionRegistrar实现
ImportBeanDefinitionRegistrar接口定义了regiserBeanDefinitions方法,从而允许我们向Spring注册必要的Bean。
@Configuration使用方式
1.@Configuration配置spring并启动spring容器
@Configuration标注到类上,相当于把该类作为spring的xml配置文件中的<beans>
package com.dxz.demo.configuration;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TestConfiguration {
public TestConfiguration() {
System.out.println("TestConfiguration容器启动初始化。。。");
}
}
主方法进行测试:
package com.dxz.demo.configuration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestMain {
public static void main(String[] args) {
// @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
// 如果加载spring-context.xml文件:
// ApplicationContext context = new
// ClassPathXmlApplicationContext("spring-context.xml");
}
}
2.@Configuration启动容器+@Bean注册Bean,@Bean下管理bean生命周期
@Bean标注在方法上,等价于spring的xml配置文件中的<bean>
package com.dxz.demo.configuration;
public class TestBean {
private String username;
private String url;
private String password;
public void sayHello() {
System.out.println("TestBean sayHello...");
}
public String toString() {
return "username:" + this.username + ",url:" + this.url + ",password:" + this.password;
}
public void start() {
System.out.println("TestBean 初始化。。。");
}
public void cleanUp() {
System.out.println("TestBean 销毁。。。");
}
}
package com.dxz.demo.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class TestConfiguration {
public TestConfiguration() {
System.out.println("TestConfiguration容器启动初始化。。。");
}
// @Bean注解注册bean,同时可以指定初始化和销毁方法
// @Bean(name="testBean",initMethod="start",destroyMethod="cleanUp")
@Bean
@Scope("prototype")
public TestBean testBean() {
return new TestBean();
}
}
package com.dxz.demo.configuration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestMain {
public static void main(String[] args) {
// @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
// 如果加载spring-context.xml文件:
// ApplicationContext context = new
// ClassPathXmlApplicationContext("spring-context.xml");
//获取bean
TestBean tb = (TestBean) context.getBean("testBean");
tb.sayHello();
}
}
@Bean支持两种属性,即initMethod和destroyMethod,这些属性可用于定义生命周期方法。在实例化bean或即将销毁它时,容器便可调用生命周期方法,使用@Bean注解的bean也支持JSR-250规定的标准@PostConstruct和@PreDestroy注解。
3.在@Configuration中引入spring的xml配置文件
package com.dxz.demo.configuration2;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource("classpath:applicationContext-configuration.xml")
public class WebConfig {
}
4.在@Configuration中引其它注解配置
package com.dxz.demo.configuration2;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import com.dxz.demo.configuration.TestConfiguration;
@Configuration
@ImportResource("classpath:applicationContext-configuration.xml")
@Import(TestConfiguration.class)
public class WebConfig {
}
5.@Configuration嵌套(嵌套的Configuration必须是静态类)
package com.dxz.demo.configuration3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.dxz.demo.configuration3")
public class TestConfiguration {
public TestConfiguration() {
System.out.println("TestConfiguration容器启动初始化。。。");
}
@Configuration
static class DatabaseConfig {
@Bean
DataSource dataSource() {
return new DataSource();
}
}
}
3.Spring容器的扩展点
Spring中BeanFactoryPostProcessor和BeanPostProcessor都是Spring初始化bean时对外暴露的扩展点。
通过这两种方式,我们可以在运行时收集到用户的配置信息,同时向Spring注册实际处理这些配置信息的Bean。
1.BeanFactoryPostProcessor
这类提供了一个方法:postProcessBeanFactory,此方法会在容器初始化过程中调用,调用时机是所有bean的定义信息都已经初始化好,但是这些bean还没实例化。
可以定义多个BeanFactoryPostProcessor,通过设置‘order’属性 来确定各个BeanFactoryPostProcessor执行顺序。
2.BeanPostProcessor
提供了两个方法:
(1)postProcessBeforeInitialization:会在每个bean实例化之后、初始化之前被调用。
(2)postProcessAfterInitialization则在每个bean初始化之后被调用。
我们常用的@Autowired注解就是通过postProcessBeforeInitialization实现的。