Spring学习6(6)
基于Java类的配置
使用Java类提供Bean的定义信息
普通的PoJo只要标注了@Configuration
注解就可以为Spring容器提供Bean的定义信息,每个标注了@Bean
的方法都相当于提供了一个Bean的定义信息代码如下:
package com.smart.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConf{
@Bean
public UserDao userDao() {
return new UserDao();
}
@Bean
public LogDao logDao() {
return new LogDao();
}
@Bean
public LogonService logonService() {
LogonService logonService = new LogonService();
logonService.setLogDao(logDao());
logonService.setUserDao(userDao());
return logonService;
}
}
使用Bean的类型由方法返回值决定,名称默认和方法名相同,也可以通过入参显示的只当Bean的id如@Bean(name="userDao")
。@Bean
所标注的方法提供了Bean的实例化逻辑。
如果Bean在多个@Configuration
配置类中定义,如何引用不同配置类中定义的Bean呢?例如UserDao和LogDao在DaoConfig中定义,logonService在ServiceConfig中定义:
package com.smart.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Daoconfig{
@Bean
public UserDao userDao() {
return new UserDao();
}
public LogDao logDao(){
return new LogDao();
}
}
需要知道的是@Configuration
本身已经带有@Component
了,所以其可以像普通Bean一样注入其他Bean中。ServiceConfig的代码如下:
package com.smart.conf;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
public class ServiceConfig{
@Autowired
private Daoconfig daoConfig;
@Bean
public LogonService logonService() {
LogonService logonService = new LogonService();
logonService.setLogDao(daoConfig.logDao());
logonService.setUserDao(daoConfig.userDao());
return logonService;
}
}
不过这里不再是简单的使用类中定义的方法逻辑了,而是返回spring容器中相应的单例。当然我们也可以在@Bean
上标注@Scope
来定义范围如:
@Scope("prototype")
@Bean
public UserDao userDao() {
return new UserDao();
}
由于Spring容器自动对@Configuration
的类进行改造(AOP增强)以植入spring容器对Bean的管理逻辑,所以使用java类配置需要将spring aop和CGLIB类包放入路径中。
使用基于Java类的配置信息启动spring容器
直接通过@Configuration类启动Spring容器
Spring提供了一个AnnotationConfigApplicationContext类,可以直接使用标注了@Configuration
的Java类。
public class JavaConfigTest{
@Test
public void javaconf1(){
ApplicationContext ctx = new
AnnotationConfigApplicationContext(AppConf.class);
LogonService logonService = ctx.getBean(LogonService.class);
logonService.printHello();
}
}
上述代码的意思是通过AnnotationConfigApplicationContext类的构造函数直接传入标注@Configuration
的java类。其实其还可以通过编码的方式加载多个类,并且通过刷新容器应用这些配置类。
@Test
public void javaconf2() {
AnnotationConfigApplicationContext ctx = new
AnnotationConfigApplicationContext();
ctx.register(Daoconfig.class);
ctx.register(ServiceConfig.class);
ctx.refresh();
LogonService logonService = ctx.getBean(LogonService.class);
logonService.printHello();
}
同样的,这种方法也可以合并配置类,在定义中加入@Import
注解就可以在内存中合并多个配置文件。
通过XML配置文件
因为标注了@Configuration
同@Component
一样也是一个Bean,所以可以通过和注解相同的方法,利用<context:component-scan>
的标签来扫描。
引用xml配置信息
在使用java配置的方法时,还可以使用ImportResource来注入来自xml配置方法配置的bean。假设我们创建一个beans3.xml,其中代码如下:
<bean id="userDao" class="com.smart.conf.UserDao"/>
<bean id="logDao" class="com.smart.conf.LogDao"/>
而后在logon的配置文件中采用如下方式注入:
@Configuration
@ImportResource("classpath:com/smart/conf/Beans3.xml")
public class LogonAppConfig{
@Bean
@Autowired
public LogonService logonService(UserDao userDao, LogDao logDao) {
LogonService logonService = new LogonService();
logonService.setUserDao(userDao);
logonService.setLogDao(logDao);
return logonService;
}
}
基于Groovy DSL配置
使用Groovy DSL提供Bean定义信息
Groovy DSL进行Bean定义配置时,类似于XML的配置,不过配置信息是通过Groovy来表达的。我们在resources.com.smart.groovy下创建spring-context.groovy
import com.smart.groovy.LogDao
import com.smart.groovy.LogonService
import com.smart.groovy.UserDao
import com.smart.groovy.XmlUserDao
import com.smart.groovy.DbUserDao
import org.springframework.core.io.ClassPathResource
beans{
//1.声明context命名空间
xmlns context:"http://www.springframework.org/schema/context"
//2.与注解混合使用,定义注解Bean扫描包路径
context.'component-scan'('base-package':"com.smart.groovy"){
//3.排除不需要扫描的包路径
'exclude-filter'('type':"aspectj", 'expression':"com.smart.xml.*")
}
//4.读取app-conf.properties配置文件
def stream;
def config = new Properties();
try{
stream = new ClassPathResource('conf/app-conf.properties').inputStream
config.load(stream);
}finally{
if(stream!=null){
stream.close()
}
}
//5.配置无参构造函数Bean
logDao(LogDao){
bean->
bean.scope="prototype"
bean.initMethod="init"
bean.destroyMethod="destory"
bean.lazyInit=true
}
//6.根据条件注入Bean
if("db"==config.get("dataProvider")){
userDao(DbUserDao)
}else{
userDao(XmlUserDao)
}
//7.配置有参构造函数注入Bean,参数是userDao
logonService(LogonService,userDao){
logDao=ref("logDao")//8.配置属性注入,引用Groovy定义Bean
mailService = ref("mailService")//9.配置属性注入,引用定义注解
}
}
这里注释4处是Groovy脚本加载资源配置文件。其中内容为:
dataProvider=db
这句话在注释6中起到了选择要注入的Bean的作用。
注释5是注入了一个无构造函数的Bean,其中logDao是定义的Bean的名称,括号内是类名。
注释7定义了一个带有构造函数的Bean,括号内第一个参数是Bean的类名,第二个参数是构造参数,并且这里用ref的方法注入了属性。
可以看到这里用到了很多Dao类,其中内容不是关键,这里将这些类的内容给出如下:
package com.smart.groovy;
import org.springframework.stereotype.Component;
public interface UserDao {
}
public class LogDao {
private String dataProvider;
public void saveLog(){}
public void setDataProvider(String dataProvider) {
this.dataProvider = dataProvider;
}
public void init(){
System.out.println("initMethod....");
}
public void destory(){
System.out.println("destoryMethod....");
}
}
package com.smart.groovy;
public class XmlUserDao implements UserDao {
}
package com.smart.groovy;
public class LogonService {
private UserDao userDao;
private LogDao logDao;
private MailService mailService;
public LogonService(UserDao userDao) {
this.userDao = userDao;
}
public void setLogDao(LogDao logDao) {
this.logDao = logDao;
}
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
public MailService getMailService() {
return mailService;
}
}
package com.smart.groovy;
import org.springframework.stereotype.Component;
@Component
public class MailService {
}
启动spring容器
spring为基于Groovy的配置提供了专门的ApplicationContext实现类:GenericGroovyApplicationContext。实例如下:
package com.smart.groovy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericGroovyApplicationContext;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
public class GroovyTest{
@Test
public void getBeans() {
//加载Groovy Bean配置文件
ApplicationContext ctx = new GenericGroovyApplicationContext(""
+ "classpath:com/smart/groovy/spring-context.groovy");
//加载Bean
LogonService logonService = ctx.getBean(LogonService.class);
assertNotNull(logonService);
//加载注解定义的Bean
MailService mailService = ctx.getBean(MailService.class);
assertNotNull(mailService);
//判断是否注入DbUserDao
UserDao userDao = ctx.getBean(UserDao.class);
assertTrue(userDao instanceof DbUserDao);
}
}
这里test需要注意放上aspectj和Groovy的依赖。
还需要注意这里的mailService是不需要的,在logonService就已经被注入了,这里只是为了测试一下注解。
通过编码方式动态添加Bean(此节暂时略过)
通过DefaultListableBeanFactory
DefaultListableBeanFactory实现了ConfigurableListableBeanFactory接口,提供了可扩展配置等功能,可以通过此类实现Bean动态注入。
为了保证动态注入的Bean也能被AOP增强,需要实现BeanFactoryPostProcessor接口,下面是一个实例:
package com.smart.dynamic;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class UserServiceFactoryBean implements BeanFactoryPostProcessor{
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf)
throws BeansException{
//将ConfigurableListableBeanFactory转化为DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)bf;
//通过BeanDefinitionBuilder创建Bean定义
BeanDefinitionBuilder beanDefinitionBuilder =
BeanDefinitionBuilder.genericBeanDefinition(UserService.class);
//设置属性userDao,此属性引用已经定义的bean:userDao
beanDefinitionBuilder.addPropertyReference("userDao","userDao");
//可以注册一个Bean定义
beanFactory.registerBeanDefinition("userService1", beanDefinitionBuilder.getRawBeanDefinition());
//也可以直接注册一个Bean实例
beanFactory.registerSingleton("userService2", new UserService());
}
}
扩展自定义标签
在开发产品级组件时,我们会将组件标签化,spring为第三方组件自定义标签提供了支持,需要经过下面几个步骤:
1. 采用XSD描述自定义标签元素属性
第一步是定义标签元素的XML结构,采用XSD描述自定义标签的元素属性,我们在src.main.resources.com.smart.schema下创建userservice.xsd,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.smart.com/schema/service"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.smart.com/schema/service"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="user-service">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="dao" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
这里targetNamespace是指定一个自定义标签的命名空间,<xsd:element>
是定义了一个user-service标签并在beans:identifiedType基础上定义了user-service标签的扩展属性“dao”。
编写Bean定义的解析器
接下来编写用户服务标签解析类
public class UserServiceDefinitionParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserService.class);
//获取自定义标签属性
String dao = element.getAttribute("dao");
beanDefinitionBuilder.addPropertyReference("userDao",dao);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
parserContext.registerBeanComponent(new BeanComponentDefinition( beanDefinition,"userService"));
return null;
}
}
注册自定义标签解析器
上面一步完成了解析器的步骤,所以接下来要将解析器注册到spring命名空间
package com.smart.dynamic;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class UserServiceNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("user-service", new UserServiceDefinitionParser());
}
}
绑定命名空间解析器
在resource.META-INF创建spring.handlers和spring.schemas两个文件,告诉自定义标签的文档结构以及解析它的类:
在spring.schemas文件下:
http\://www.smart.com/schema/service.xsd=com/smart/schema/userservice.xsd
在spring.handlers文件下:
http\://www.smart.com/schema/service=com.smart.dynamic.UserServiceNamespaceHandler
使用实例:
我们可以在配置文件中使用user-service标签了,需要声明命名空间,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:us="http://www.smart.com/schema/service"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.smart.com/schema/service http://www.smart.com/schema/service.xsd">
<bean id="userDao" class="com.smart.dynamic.UserDao" />
<us:user-service dao="userDao"/>
</beans>