1. 程式人生 > >註解驅動開發一元件新增

註解驅動開發一元件新增

【1】@Configuration和@bean

從Spring3.0開始,@Configuration用於定義配置類,可替換xml配置檔案,被註解的類內部包含有一個或多個被@Bean註解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。

@Configuration標註在類上,相當於把該類作為spring的xml配置檔案中的<beans>,作用為:配置spring容器(應用上下文)。

AnnotationConfigWebApplicationContext類繼承圖如下所示:

在這裡插入圖片描述

AnnotationConfigApplicationContext類繼承圖如下所示:

在這裡插入圖片描述

配置類例項如下:

package com.web.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.ComponentScans;

import com.web.bean.Person;

//配置類==配置檔案
@Configuration  //告訴Spring這是一個配置類
public class MainConfig {
	//給容器中註冊一個Bean;型別為返回值的型別,id預設是用方法名作為id
	@Bean("person")
	public Person person01(){
		return new Person("lisi", 20);
	}

}

測試類如下:

package com.web.test;

import java.util.Map;

import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import com.web.config.MainConfig;

public class IOCTest {

/*用AnnotationConfigApplicationContext 替換ClassPathXmlApplicationContext。
如果載入spring-context.xml檔案:
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
*/
	AnnotationConfigApplicationContext applicationContext = 
	new AnnotationConfigApplicationContext(MainConfig.class);

	@SuppressWarnings("resource")
	@Test
	public void test01(){
		AnnotationConfigApplicationContext applicationContext = 
		new AnnotationConfigApplicationContext(MainConfig.class);
		// 獲取容器中註冊的bean的資訊
		String[] definitionNames = applicationContext.getBeanDefinitionNames();
		for (String name : definitionNames) {
			System.out.println(name);
		}
	}
}

【2】@Bean 註解##

@Bean是一個方法級別上的註解,效果等同於在xml中配置一個bean,並設定其屬性。

xml配置如下:

<bean id="person" 
	class="com.core.Person" scope="singleton"  
	init-method="init"  destroy-method="cleanUp"
	autowire="byName" lazy-init="true" >  
</bean> 

等同於如下:

//給容器中註冊一個Bean;型別為返回值的型別,id預設是用方法名作為id
	@Scope("singleton")
	@Lazy
	@Bean(name="person",initMethod="init",destroyMethod="cleanUp",
	autowire=Autowire.BY_NAME)
	public Person person01(){
		return new Person("lisi", 20);
	}

Spring容器中註冊的bean,預設是單例項的。其作用域說明如下:

ConfigurableBeanFactory#SCOPE_PROTOTYPE    
ConfigurableBeanFactory#SCOPE_SINGLETON  
org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST  request
org.springframework.web.context.WebApplicationContext#SCOPE_SESSION	 sesssion

@Scope:調整作用域

prototype:

多例項的:ioc容器啟動並不會去呼叫方法建立物件放在容器中。每次獲取的時候才會呼叫方法建立物件。

singleton:

單例項的(預設值):ioc容器啟動會呼叫方法建立物件放到ioc容器中。以後每次獲取就是直接從容器(map.get())中拿。

request:同一次請求建立一個例項。

session:同一個session建立一個例項。

需要注意的是,單例項bean 初始化方法在容器建立時被呼叫,銷燬方法在容器銷燬時被呼叫。多例項bean,初始化方法在第一次獲取bean的時候呼叫(非容器建立時,容器建立時會呼叫構造方法),銷燬方法容Spring 容器不負責管理。

懶載入說明如下:

單例項bean:預設在容器啟動的時候建立物件;

懶載入:容器啟動不建立物件。第一次使用(獲取)Bean建立物件,並初始化。

多例項懶載入測試如下:

	@Test
	public void test02(){
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
		System.out.println("ioc容器建立完成....");
		Object bean = applicationContext.getBean("person");
		Object bean2 = applicationContext.getBean("person");
		System.out.println(bean == bean2);
	}

result as follows :

ioc容器建立完成....
給容器中新增Person....
給容器中新增Person....
false//兩個bean不同哦

關於Spring中Bean的作用域與生命週期參考博文:Spring中Bean的作用域

【3】@ComponentScan註解

xml配置中必不可少的標籤,用來掃描bean並註冊到IOC容器中。

xml配置示例如下:

<!-- 包掃描、只要標註了@Controller、@Service、@Repository,@Component 都被注入-->
 <context:component-scan base-package="com.web" use-default-filters="false">
 	<context:include-filter type="annotation" expression=""/>
 	<context:exclude-filter type="annotation" expression=""/>
 </context:component-scan>

其中,type有五種形式:

  • annotation-註解,
  • assignable-給定的型別,
  • regex-正則指定,
  • custom-自定義規則
  • aspectj-ASPECTJ表示式。

其javadoc如下:

Controls the type of filtering to apply to the expression. 
"annotation" indicates an annotation to be present at the type 
 level in target components; 
 "assignable" indicates a class (or interface) that the target components are assignable to (extend/implement); 
 "aspectj" indicates an AspectJ type pattern expression to be matched by the target components; 
 "regex"indicates a regex pattern to be matched by the target 
 components' class names; 
 "custom" indicates a custom implementation of the org.springframework.core.type.TypeFilter interface. 
 Note: This attribute will not be inherited by child bean 
 definitions. 
 Hence, it needs to be specified per concrete bean 
 definition.

配置類如下:

//配置類==配置檔案
@Configuration  //告訴Spring這是一個配置類

@ComponentScans(
	value = {
		@ComponentScan(value="com.web",includeFilters = {
			@Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
			@Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),
			@Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
		},useDefaultFilters = false)	
	}
)
//@ComponentScan  value:指定要掃描的包
//excludeFilters = Filter[] :指定掃描的時候按照什麼規則排除那些元件
//includeFilters = Filter[] :指定掃描的時候只需要包含哪些元件
//FilterType.ANNOTATION:按照註解
//FilterType.ASSIGNABLE_TYPE:按照給定的型別;
//FilterType.ASPECTJ:使用ASPECTJ表示式
//FilterType.REGEX:使用正則指定
//FilterType.CUSTOM:使用自定義規則
public class MainConfig {
	
	//給容器中註冊一個Bean;型別為返回值的型別,id預設是用方法名作為id
	@Bean(name="person")
	public Person person01(){
		return new Person("lisi", 20);
	}

}

FilterType.CUSTOM,classes={MyTypeFilter.class}中的MyTypeFilter類如下:

public class MyTypeFilter implements TypeFilter {

	/**
	 * metadataReader:讀取到的當前正在掃描的類的資訊
	 * metadataReaderFactory:可以獲取到其他任何類資訊的
	 */
	@Override
	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException {
		// TODO Auto-generated method stub
		//獲取當前類註解的資訊
		AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
		//獲取當前正在掃描的類的類資訊
		ClassMetadata classMetadata = metadataReader.getClassMetadata();
		//獲取當前類資源(類的路徑)
		Resource resource = metadataReader.getResource();
		
		String className = classMetadata.getClassName();
		System.out.println("--->"+className);
		// 這裡自定義規則
		if(className.contains("er")){
			return true;
		}
		return false;
	}

}

【4】@Conditional條件判斷

@Conditional({Condition}) : 按照一定的條件進行判斷,滿足條件給容器中註冊bean。

配置類如下:

/**
	 * @Conditional({Condition}) : 按照一定的條件進行判斷,滿足條件給容器中註冊bean
	 * 
	 * 如果系統是windows,給容器中註冊("bill")
	 * 如果是linux系統,給容器中註冊("linus")
	 */
	@Conditional(WindowsCondition.class)
	@Bean("bill")
	public Person person01(){
		return new Person("Bill Gates",62);
	}
	
	@Conditional(LinuxCondition.class)
	@Bean("linus")
	public Person person02(){
		return new Person("linus", 48);
	}

Conditional介面如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition}s that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

既可以作用於方法上面,也可以配置在類上面,引數為Condition的實現類。

Condition介面如下:

public interface Condition {

	/**
	 * Determine if the condition matches.
	 * @param context the condition context
	 * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
	 * or {@link org.springframework.core.type.MethodMetadata method} being checked.
	 * @return {@code true} if the condition matches and the component can be registered
	 * or {@code false} to veto registration.
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

條件類如下:

//判斷是否linux系統
public class LinuxCondition implements Condition {

	/**
	 * ConditionContext:判斷條件能使用的上下文(環境)
	 * AnnotatedTypeMetadata:註釋資訊
	 */
	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		// TODO是否linux系統
		//1、能獲取到ioc使用的beanfactory
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		//2、獲取類載入器
		ClassLoader classLoader = context.getClassLoader();
		//3、獲取當前環境資訊
		Environment environment = context.getEnvironment();
		//4、獲取到bean定義的註冊類
		BeanDefinitionRegistry registry = context.getRegistry();
		
		String property = environment.getProperty("os.name");
		
		//可以判斷容器中的bean註冊情況,也可以給容器中註冊bean
		boolean definition = registry.containsBeanDefinition("person");
		if(property.contains("linux")){
			return true;
		}
		
		return false;
	}

}

如果該註解配置在類上面,則該配置類的所有方法都將使用該註解。示例如下:

@Conditional({WindowsCondition.class})
@Configuration
public class MainConfig2 {
	//...
}

@Confitional擴充套件如下圖:

這裡寫圖片描述

【5】@import匯入元件

@Import ,快速給容器中匯入一個元件。

其註解如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
	 * or regular component classes to import.
	 */
	Class<?>[] value();

}

其使用方式如下:

1)@Import(要匯入到容器中的元件);容器中就會自動註冊這個元件,id預設是全類名。

@Configuration
@Import({Color.class,Red.class})
public class MainConfig2 {
	//...
}

2)ImportSelector:返回需要匯入的元件的全類名陣列;

//自定義邏輯返回需要匯入的元件
public class MyImportSelector implements ImportSelector {

	//返回值,就是到匯入到容器中的元件全類名
	//AnnotationMetadata:當前標註@Import註解的類的所有註解資訊
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		// TODO Auto-generated method stub
		//importingClassMetadata
		//方法不要返回null值
		return new String[]{"com.web.bean.Blue","com.web.bean.Yellow"};
	}
}

此時配置類如下:

@Configuration
@Import({Color.class,Red.class,MyImportSelector.class})
//@Import匯入元件,id預設是元件的全類名
public class MainConfig2 {
	//...
}

3)ImportBeanDefinitionRegistrar:手動註冊bean到容器中。

ImportBeanDefinitionRegistrar介面類如下:

public interface ImportBeanDefinitionRegistrar {

	/**
	 * Register bean definitions as necessary based on the given annotation metadata of
	 * the importing {@code @Configuration} class.
	 * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
	 * registered here, due to lifecycle constraints related to {@code @Configuration}
	 * class processing.
	 * @param importingClassMetadata annotation metadata of the importing class
	 * @param registry current bean definition registry
	 */
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

實現類如下:


public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * AnnotationMetadata:當前類的註解資訊
	 * BeanDefinitionRegistry:BeanDefinition註冊類;
	 * 		把所有需要新增到容器中的bean;呼叫
	 * 		BeanDefinitionRegistry.registerBeanDefinition手工註冊進來
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		
		boolean definition = registry.containsBeanDefinition("com.web.bean.Red");
		boolean definition2 = registry.containsBeanDefinition("com.web.bean.Blue");
		if(definition && definition2){
			//指定Bean定義資訊;(Bean的型別,Bean。。。)
			RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
			//註冊一個Bean,指定bean名
			registry.registerBeanDefinition("rainBow", beanDefinition);
		}
	}

}

此時配置類如下:

@Import({Color.class,Red.class,MyImportSelector.class,
MyImportBeanDefinitionRegistrar.class})
//@Import匯入元件,id預設是元件的全類名
public class MainConfig2 {
	//...
}

【6】FactoryBean

FactoryBean : 是一個Java Bean,但是它是一個能生產物件的工廠Bean,它的實現和工廠模式及修飾器模式很像。

其介面如下:

/**
 * Interface to be implemented by objects used within a {@link BeanFactory} which
 * are themselves factories for individual objects. If a bean implements this
 * interface, it is used as a factory for an object to expose, not directly as a
 * bean instance that will be exposed itself.
 *
 * <p><b>NB: A bean that implements this interface cannot be used as a normal bean.</b>
 * A FactoryBean is defined in a bean style, but the object exposed for bean
 * references ({@link #getObject()}) is always the object that it creates.
 *
 * <p>FactoryBeans can support singletons and prototypes, and can either create
 * objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean}
 * interface allows for exposing more fine-grained behavioral metadata.
 *
 * <p>This interface is heavily used within the framework itself, for example for
 * the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the
 * {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for
 * custom components as well; however, this is only common for infrastructure code.
 *
 * <p><b>{@code FactoryBean} is a programmatic contract. Implementations are not
 * supposed to rely on annotation-driven injection or other reflective facilities.</b>
 * {@link #getObjectType()} {@link #getObject()} invocations may arrive early in
 * the bootstrap process, even ahead of any post-processor setup. If you need access
 * other beans, implement {@link BeanFactoryAware} and obtain them programmatically.
 *
 * <p>Finally, FactoryBean objects participate in the containing BeanFactory's
 * synchronization of bean creation. There is usually no need for internal
 * synchronization other than for purposes of lazy initialization within the
 * FactoryBean itself (or the like).
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @since 08.03.2003
 * @see org.springframework.beans.factory.BeanFactory
 * @see org.springframework.aop.framework.ProxyFactoryBean
 * @see org.springframework.jndi.JndiObjectFactoryBean
 */
public interface FactoryBean<T> {

	/**
	 * Return an instance (possibly shared or independent) of the object
	 * managed by this factory.
	 * <p>As with a {@link BeanFactory}, this allows support for both the
	 * Singleton and Prototype design pattern.
	 * <p>If this FactoryBean is not fully initialized yet at the time of
	 * the call (for example because it is involved in a circular reference),
	 * throw a corresponding {@link FactoryBeanNotInitializedException}.
	 * <p>As of Spring 2.0, FactoryBeans are allowed to return {@code null}
	 * objects. The factory will consider this as normal value to be used; it
	 * will not throw a FactoryBeanNotInitializedException in this case anymore.
	 * FactoryBean implementations are encouraged to throw
	 * FactoryBeanNotInitializedException themselves now, as appropriate.
	 * @return an instance of the bean (can be {@code null})
	 * @throws Exception in case of creation errors
	 * @see FactoryBeanNotInitializedException
	 */
	T getObject() throws Exception;

	/**
	 * Return the type of object that this FactoryBean creates,
	 * or {@code null} if not known in advance.
	 * <p>This allows one to check for specific types of beans without
	 * instantiating objects, for example on autowiring.
	 * <p>In the case of implementations that are creating a singleton object,
	 * this method should try to avoid singleton creation as far as possible;
	 * it should rather estimate the type in advance.
	 * For prototypes, returning a meaningful type here is advisable too.
	 * <p>This method can be called <i>before</i> this FactoryBean has
	 * been fully initialized. It must not rely on state created during
	 * initialization; of course, it can still use such state if available.
	 * <p><b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return
	 * {@code null} here. Therefore it is highly recommended to implement
	 * this method properly, using the current state of the FactoryBean.
	 * @return the type of object that this FactoryBean creates,
	 * or {@code null} if not known at the time of the call
	 * @see ListableBeanFactory#getBeansOfType
	 */
	Class<?> getObjectType();

	/**
	 * Is the object managed by this factory a singleton? That is,
	 * will {@link #getObject()} always return the same object
	 * (a reference that can be cached)?
	 * <p><b>NOTE:</b> If a FactoryBean indicates to hold a singleton object,
	 * the object returned from {@code getObject()} might get cached
	 * by the owning BeanFactory. Hence, do not return {@code true}
	 * unless the FactoryBean always exposes the same reference.
	 * <p>The singleton status of the FactoryBean itself will generally
	 * be provided by the owning BeanFactory; usually, it has to be
	 * defined as singleton there.
	 * <p><b>NOTE:</b> This method returning {@code false} does not
	 * necessarily indicate that returned objects are independent instances.
	 * An implementation of the extended {@link SmartFactoryBean} interface
	 * may explicitly indicate independent instances through its
	 * {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean}
	 * implementations which do not implement this extended interface are
	 * simply assumed to always return independent instances if the
	 * {@code isSingleton()} implementation returns {@code false}.
	 * @return whether the exposed object is a singleton
	 * @see #getObject()
	 * @see SmartFactoryBean#isPrototype()
	 */
	boolean isSingleton();

}

自定義工廠bean實現該介面:

  • 這裡為該類添加了@Component註解。
@Component
//建立一個Spring定義的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {

	//返回一個Color物件,這個物件會新增到容器中
	@Override
	public Color getObject() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("ColorFactoryBean...getObject...");
		return new Color();
	}

	@Override
	public Class<?> getObjectType() {
		// TODO Auto-generated method stub
		return Color.class;
	}

	//是單例?
	//true:這個bean是單例項,在容器中儲存一份
	//false:多例項,每次獲取都會建立一個新的bean;
	@Override
	public boolean isSingleton() {
		// TODO Auto-generated method stub
		//return true;
		return false;
		
	}

}

測試如下:

public class IOCTest {
	AnnotationConfigApplicationContext applicationContext =
	new AnnotationConfigApplicationContext(MainConfig2.class);
	
	
	@Test
	public void testImport(){
		//工廠Bean獲取的是呼叫getObject建立的物件
		Object bean2 = applicationContext.getBean("colorFactoryBean");
		Object bean3 = applicationContext.getBean("colorFactoryBean");
		System.out.println("bean的型別:"+bean2.getClass());
		System.out.println(bean2 == bean3);
		
	}

result as follows :

ColorFactoryBean...getObject...
ColorFactoryBean...getObject...
bean的型別:class com.web.bean.Color
false

可以看到根據id colorFactoryBean獲取的實際bean為com.web.bean.Color!並且兩次獲取的bean不等。

如果要獲取工廠bean本身,則如下:

Object bean4 = applicationContext.getBean("&colorFactoryBean");
System.out.println(bean4.getClass());

result as follows :

class com.web.bean.ColorFactoryBean

原理如下圖:

這裡寫圖片描述

那麼Servlet、Filter和Listener如何使用程式碼方式注入容器呢?

參考博文: