註解驅動開發一元件新增
【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如何使用程式碼方式注入容器呢?
參考博文: