1. 程式人生 > >Spring學習6(6)基於java類,Groovy,編碼的方式對Bean進行配置

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>