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实现的。