文章目錄
在學習spring中的註解開發之前,必須瞭解java中的反射和註解:
1. 組件添加相關注解
當一個項目很大的時候,xml 文件就會有很多,配置起來就稍微有點麻煩,所以,可以使用註解來代替在xml的相關配置。
1.1 @Configuration
用在類上,指明這個類是配置類,相當於是一個只有名稱空間的xml文件,有了配置類,就可以不用寫xml文件了。
1.2 @Bean
用在方法上,給IOC容器中註冊一個bean,id 默認爲方法名,也可以使用value屬性指定,類型是方法的返回值類型。相當於在xml文件中寫<bean id=" " class=" "/></bean>
。如果方法中有參數,參數默認從容器中獲取。
`
@//表示這個類是配置類,代替xml文件
@Configuration
public class MainConfig {
@Bean //在IOC容器中註冊bean,類型是返回值類型,id默認是方法名
public Person person(){
return new Person("張三",20);
}
}
1.3 @ComponentScan
用在配置類上,開啓包掃描,開啓之後,類只要標註了@Controller、@Service、@repository、@Componmente
這四個註解中的任何一個,都會在容器中註冊,相當於在xml文件中使用<context:componment-scan value=" ">
- 常用屬性:
- value:指定要掃描的包
- excludeFilters :指定掃描的時候按照什麼規則排除哪些組件,這個屬性的類型是一個Filter註解類型的數組,Filter註解中,有type(按照什麼類型進行排除)、classes(排除type類型的類)等屬性
@ComponentScan(value="ewen",
excludeFilters = {
排除@Repository註解
@Filter(type=FilterType.ANNOTATION,classes={Repository.class})
})
- includeFilters:指定掃描的時候只包含那些類型的組件,使用這個屬性的時候,需要讓
useDefaultFilters=false
(@CoponentScan的屬性),來禁用默認的過濾規則,默認使用的是不掃描哪些些組件
@ComponentScan(value="ewen",
includeFilters ={
//只要@Service註解
@Filter(type=FilterType.ANNOTATION, classes={Service.class, Repository.class})
}, useDefaultFilters = false)
@Filter這個註解中,type的類型是FilterType枚舉類,枚舉類中有以下幾個值
- FilterType.ANNOTATION:按照註解(常用),classes是註解.class
- FilterType.ASSIGNABLE_TYPE:按照給定的類型(常用),classes是類名.class
- FilterType.ASPECTJ:使用ASPECTJ表達式
- FilterType.REGEX:使用正則指定
- FilterType.CUSTOM:使用自定義規則(自定義類,實現FilterType接口),classes爲自定義類.class
自定義過濾規則,就是寫一個類,實現FilterType接口,實現match方法(過濾規則),當掃描一個類時,會執行match方法,如果返回true,表示,符合規則,將這個類在IOC容器中註冊,返回false,不在容器中註冊。
@ComponentScan(value="ewen",
includeFilters = {
@Filter(type=FilterType.CUSTOM, classes={MyTypeFilter.class})
} , useDefaultFilters = false
)
自定義規則(只要含有 “er” 的類)
public class MyTypeFilter implements TypeFilter {
/**
* @param metadataReader:讀取到的當前正在掃描的類信息
* @param metadataReaderFactory:可以獲取到其他類的任何信息
* @return 當進行包掃描時,使用自定義過濾規則,掃描到的類來與這個方法中定義的規則匹配
* 如果返回true,代表符合規則,將這個類注入到容器中,返回false,代表不符合規則
* @throws IOException
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//獲得當前正在掃描類的註解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//獲取當前正在掃描的類信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//獲取當前正在掃描類的路徑
Resource resource = metadataReader.getResource();
String name = classMetadata.getClassName();
if(name.contains("er")){
return true;
}
return false;
}
}
1.4 @Scope
指定bean的作用範圍,有如下四個屬性:
- singleton:單實例的,默認值,會在IOC容器啓動的時候,創建對象(執行@Bean下的方法),並且,只創建一次,每一次拿到的是同一個對象
- prototype:多實例的,從容器中獲取對象的時候創建,每次獲取,都會執行@Bean下的方法,得到不同的對象
- request:同一次請求創建一個實例(很少用)
- session:同一個session創建一個實例(很少用)
1.5 @Lazy
懶加載,容器啓動時,不創建對象,獲取時纔會創建,只有當是bean是單例的(singleton)時纔有用
1.6 @Conditional
用在方法上,按照一定條件進行判斷,滿足條件給容器註冊bean,注意需要和@Bean一起使用。也可以把這個註解放在類上,類中的bean滿足條件纔可以在容器中註冊
需要定義一個條件類,實現Condition接口,實現match方法,如果這個方法返回true,則符合條件,註冊bean
//如果是Windows操作系統,給容器中註冊bill
@Conditional(WindowsCondition.class)
@Bean
public Person person01(){
return new Person("bill", 60);
}
自定義條件類
//判斷當前操作系統是不是Windows
public class WindowsCondition implements Condition {
/**
*
* @param conditionContext:判斷條件能使用的上下文環境
* @param annotatedTypeMetadata:註釋信息
* @return 如果返回true,代表符合條件,返回false,表示不符合條件
*/
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
/*通過conditionContext可以獲得很多有用的信息, 可以使用這些信息,來設置很多條件
//1.獲取IOC使用的工廠BeanFactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
//2.獲取類加載器
ClassLoader classLoader = conditionContext.getClassLoader();
//4.獲取bean定義的註冊類,通過這個類可以註冊、查詢、移除IOC容器中的bean
BeanDefinitionRegistry registry = conditionContext.getRegistry();
*/
//3. 獲取運行時的環境信息
Environment environment = conditionContext.getEnvironment();
//獲取當前的操作系統名字
String osName = environment.getProperty("os.name");
//判斷當前操作系統是不是Windows 10
if(osName.contains("Windows")){
return true;
}
return false;
}
}
1.7 @Import
給容器中註冊組件(bean),前面已經寫了兩種:
- 包掃描+組件標註註解(@Service / @Controller / @Repository / @Componment),這種方式比較侷限,只能用在自己寫的類上
- @Bean 可以註冊第三方包裏面的組件,使用類的構造函數完成
@Import(類名.class) 快速給容器中導入一個組件,id爲類的全路徑名,用在類上,屬性爲class類型的數組
比如,在容器中註冊Color、Red類
@Configuration
//@Import:快速在容器中註冊組件,id爲類的全路徑名
@Import({Color.class, Red.class})
public class MainConfig02 {
。。。}
- @Import中,還可以寫一個選擇器ImportSelector。選擇器需要自己寫一個類,實現ImportSelector接口,實現selectImports方法,這個方法返回一個String的數組,數組中就是要註冊的bean的全路徑名。
@Configuration
/* @Import:快速在容器中註冊組件,id爲類的全路徑名
類名.class
選擇器類.class
*/
@Import({Color.class, Red.class, MyImportSelector.class})
public class MainConfig02 {
。。。}
自定義選擇器類
//選擇器類
public class MyImportSelector implements ImportSelector {
/**
* @param importingClassMetadata:可以獲得@Import標註的類的所有註解信息
* @return 返回一個String數組,裏面要註冊的bean的全路徑名,id是全路徑名
*/
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"ewen.bean.Blue"};
}
}
- @Import 中還可以寫一個註冊器類,該類實現ImportBeanDefinitionRegistrar接口,實現registerBeanDefinitions方法,在方法中可以手工註冊bean,並且設置bean的id。
@Configuration
/* @Import:快速在容器中註冊組件,id爲類的全路徑名
類名.class
選擇器類.class:實現ImportSelector接口的類
註冊類.class:實現ImportBeanDefinitionRegistrar接口的類
*/
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig02 {
。。。}
註冊類
//註冊類
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
AnnotationMetadata:@Import標註類的註解信息
BeanDefinitionRegistry:bean的註冊類,通過這個類,調用registerBeanDefinition方法
可以手動的在容器中註冊bean。也可以用這個類中的方法進行查詢、移除bean
*/
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//先判斷IOC容器中是否有藍色和紅色,如果都有,就註冊一個RainBow
boolean b1 = registry.containsBeanDefinition("ewen.bean.Blue");
boolean b2 = registry.containsBeanDefinition("ewen.bean.Red");
if(b1 && b2){
//指定bean的定義信息(bean的類型、作用域等等)
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
//指定bean的id
registry.registerBeanDefinition("rainBow",beanDefinition);
}
}
}
1.8 FactoryBean
使用spring提供的FactoryBean(工廠bean)也可以給容器註冊bean。FactoryBean 是一個接口,裏面有三個方法,getObject,獲取對象,getObjectType,獲取對象類型,isSingleton,是否是單例模式。定義一個類實現FactoryBean接口,實現上面三個方法,然後在配置類中,使用@Bean註解來註冊這個類,需要注意的是,通過getBean方法獲取的是在getObject中生成的對象,而不是FactoryBean對象,可以在name前面加一個“&”獲取FactoryBean 對象。
//自定義的FactoryBean,通過這個FactoryBean來給容器註冊Bean
public class ColorFactoryBean implements FactoryBean<Color> {
public Color getObject() throws Exception {
return new Color();
}
public Class<?> getObjectType() {
return Color.class;
}
//返回true,是單例,返回false不是單例
public boolean isSingleton() {
return true;
}
}
配置類
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
2. Bean 的聲明週期
bean創建—初始化—銷燬的過程
- bean的創建:單實例:在容器啓動的時候創建對象。多實例:在每次獲取的時候創建對象。
- 初始化:對象創建完成,並且賦值好,調用初始化方法。
- 銷燬:單實例:容器關閉的時候。多實例:容器不會管理這個bean,容器不會調用銷燬的方法。
2.1 容器管理bean的生命週期
我們可以自定義初始化和銷燬方法,容器在bean進行到當前生命週期的時候來調用自定義的初始化和銷燬方法。
指定bean的初始化和銷燬方法
- 通過@Bean註解,指定initMethod和destoryMethod 屬性
- 通過讓Bean實現InitializingBean(定義初始化邏輯),DisposableBean(定義銷燬邏輯)接口;
- 使用@PostConstruct:在bean創建完成並且屬性賦值完成,來執行初始化方法
@PreDestroy:在容器銷燬bean之前通知我們進行清理工作 - BeanPostProcessor【interface】:bean的後置處理器,在bean初始化前後進行一些處理工作,需要定義一個類實現該接口,實現下面兩個方法
postProcessorBeforeInitialization:在初始化之前工作
postProcessAfterInitialization:在初始化之後工作
BeanPostProcessor 原理
- 調用populateBean方法給bean賦好值,執行initializeBean方法
- nitializeBean方法中主要執行applyBeanPostProcessorsBeforeInitialization 遍歷得到容器中所有的BeanPostProcessor;挨個執行beforeInitialization,一但返回null,跳出for循環,不會執行後面的postProcessBeforeInitialization方法
- 執行完applyBeanPostProcessorsBeforeInitialization ,執行初始化方法,再執行applyBeanPostProcessorsAfterInitialization方法
Spring底層對 BeanPostProcessor 的使用
bean賦值,注入其他組件,@Autowired,生命週期註解功能,@Async等都使用的是xxx BeanPostProcessor。
3. 屬性注入相關注解
3.1 @Value
xml中是在標籤下使用進行屬性注入的,可以使用@Value註解代替,@Value中,可以寫:
- 普通字符串
- SpEL(Spring Expression Language):#{ },裏面可以進行運算
- 使用${}從配置文件中獲取,必須在配置類中使用@PropertySource註解讀取外部配置文件的k/v並保存到運行的環境變量中
3.2 @Autowired
自動注入,默認優先根據類型在容器中找對應的組件,將組件賦給@Autowired下的屬性,如果容器中有多個相同類型的組件,會將屬性名作爲id在容器中查找組件。
- @Qualifier:使用@Qualifier註解來指定,要注入的bean的id,而不是使用屬性值作爲id
- @Primary:使用@Primary來指定首選的bean,這樣,spring自動注入的時候,會將首選的bean注入
如果容器中沒有相同類型的bean或者指定id的bean,會報錯,可以修改required屬性爲false(默認爲true),就不報錯了。
@Autowired 還可以用在構造器、方法、參數上,參數默認從容器中獲取。
3.3 @Resouce 和 @Inject
@Resouce 和 @Inject 都可以自動注入,@Resource(name="") 是根據bean的id自動注入的,不支持@Primary來指定首選bean。使用@Inject 要導入javax.inject包,註解裏沒有屬性。這兩個註解式java規範的,@Autowired是spring定義的。
3.4 XXXAware
自定義組件想要使用Spring容器底層的一些組件(ApplicationContext,BeanFactory,xxx),自定義組件類可以實現xxxAware;在創建對象的時候,會調用接口規定的方法注入相關組件;Aware,把Spring底層一些組件注入到自定義的Bean中;
xxxAware:底層使用xxxAwareProcessor, 如ApplicationContextAware ==》ApplicationContextAwareProcessor。在bean中獲取applicationContext。
//實現ApplicationContextAware接口,可以在Color類中獲得applicationContext
//實現BeanNameAware接口,可以獲得bean的id
//實現EmbeddedValueResolverAware接口,可以解析字符串
@Component
public class Color implements ApplicationContextAware, BeanNameAware,EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void setBeanName(String name) {
System.out.println("bean 的name:" + name);
}
//StringValueResolver解析字符串
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String s = resolver.resolveStringValue("你好${os.name} #{18 * 20}");
System.out.println("解析的字符串:" + s);
}
3.5 @Profile
指定組件在哪個環境的情況下才能被註冊到容器中,不指定,任何環境下都能註冊這個組件
比如,在tes環境下,將dataSourceTest註冊到容器,開發dev環境下,將dataSourceDev註冊到容器
參考:
Spring註解驅動教程