天天看點

Spring使用注解驅動開發一元件添加

【1】@Configuration和@Bean

從Spring3.0開始,​

​@Configuration​

​​用于定義配置類,可替換xml配置檔案,被注解的類内部包含有一個或多個被@Bean注解的方法,這些方法将會被​

​AnnotationConfigApplicationContext​

​​或​

​AnnotationConfigWebApplicationContext​

​類進行掃描,并用于建構bean定義,初始化Spring容器。

​@Configuration​

​​标注在類上,相當于把該類作為spring的xml配置檔案中的​

​<beans>​

​,作用為:配置spring容器(應用上下文)。

AnnotationConfigWebApplicationContext類繼承圖如下所示:

Spring使用注解驅動開發一進制件添加

AnnotationConfigApplicationContext類繼承圖如下所示:

Spring使用注解驅動開發一進制件添加

配置類執行個體如下:

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);
    }
  }
}      

@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的作用域。

【2】@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;
  }

}      

【3】@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擴充如下圖:

Spring使用注解驅動開發一進制件添加

【4】@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) {
    
    //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 {
  //...
}      

【5】FactoryBean

​FactoryBean​

​ : 是一個Java Bean,但是它是一個能生産對象的工廠Bean,它的實作和工廠模式及修飾器模式很像。

自定義工廠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      

原理如下圖:

Spring使用注解驅動開發一進制件添加

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

參考博文: