Spring學習6(6)基於java類,Groovy,編碼的方式對Bean進行配置
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>