Spring的注解介紹
Spring 5.x中常見的注解包括@Controller、@Service、@Repository。當我們研究Spring Boot源碼時,會發現實際上提供了更多的注解。了解這些注解對于我們非常重要,盡管目前可能還用不到它們。
核心基礎注解
注解 | 功能 |
@Bean | 器中注冊元件,代替來的标簽 |
@Configuration | 聲明這是一個配置類,替換以前的配置xml檔案 |
@ComponentScan | 包掃描,掃描@Controller、@Service、@Repository、@Component注入Spring容器中 |
@Conditional | 按條件注入 |
@Primary | 同類元件如果多個,标注主元件 |
@Lazy | 元件懶加載(最後使用的時候才建立) |
@Scope | 聲明元件的作用範圍(SCOPE_PROTOTYPE, SCOPE_SINGLETON) |
@DependsOn | 元件之間聲明依賴關系 |
@Component | @Controller、@Service、@Repository的通用注解 |
@Indexed | 加速注解,所有标注了 @Indexed 的元件直接會啟動快速加載 |
@Order | 數字越小優先級越高,越先工作 |
@Import | 導入第三方JAR包中的組,或定制批量導入元件邏 |
@ImportResource | 導入以前的XML配置檔案,讓其生效 |
@Profile | 基于多環境激活 |
@PropertySource | 外部properties配置檔案和JavaBean進行綁定,結合ConfigurationProperties |
@PropertySources | @PropertySource組合注解 |
@Autowired | 自動裝 |
@Qualifier | 精确指定 |
@Value | 取值、計算機環境變量、JVM系統。@Value("#34;) |
@Lookup | 單例元件依賴非單例元件,非單例元件擷取需要使用方法 |
注意:@Indexed 需要引入依賴org.springframework spring-context-indexer true
核心注解分析
### @Configuration和@Bean
在早期的Spring項目中,我們通常需要建立一個名為springContext.xml的配置檔案,并通過标簽進行注入。然而,通過使用 @Configuration 注解,我們可以将一個普通的Java類充當配置檔案,取代了繁瑣的xml配置。而使用 @Bean 注解則可以替代原來的 标簽。
xml檔案的配置
xml複制代碼<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanSample" class="com.xxx.xxx.BeanSample"></bean>
</beans>
configuration的配置
java複制代碼@Configuration
public class SpringConfig {
@Bean("beanSample")
BeanSample beanSample() {
return new BeanSample ();
}
}
public class SpringTest {
@Test
public void beanSample() {
// 通過配置檔案的方式擷取所有的bean
ApplicationContext context = new ClassPathXmlApplicationContext("springContext.xml");
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println("xml=>" + beanDefinitionName);
}
// 最後列印:xml=>user
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beanDefinitionName = annotationConfigApplicationContext.getBeanDefinitionNames();
for (String name : beanDefinitionName) {
System.out.println("annotation = > " + name);
}
// 最後列印去處預設配置後:
// annotation = > springConfig
// annotation = > beanSample
}
}
注意:@Bean配置的時候,方法名為預設的id類型,當然也可以@Bean("id")這樣來指定id。另外,@Bean注入的對象預設都是單例的。
@Lazy && @Scope
如前所述,通過使用@Bean注解,我們可以将對象直接注冊到容器中。那麼如何設定注冊的對象為原型作用域呢?是否可以進行懶加載呢?
首先,要将注冊的對象設定為原型作用域,隻需要在 @Bean 注解上添加 @Scope("prototype") 即可。這樣,每次從容器中擷取該對象時,都會建立一個新的執行個體。
java複制代碼// SpringConfig.class
/**
* @Scope可選内容為:
* ConfigurableBeanFactory#SCOPE_PROTOTYPE 原型模式
* ConfigurableBeanFactory#SCOPE_SINGLETON 單例模式
* org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request模式,同一次強求一個對象
* org.springframework.web.context.WebApplicationContext#SCOPE_SESSION session模式,同一個session一個對象
*/
@Bean("beanSample")
@Scope("prototype")
BeanSample beanSample() {
return new BeanSample ();
}
@Bean("beanSample2")
@Scope("singleton")
BeanSample beanSample2() {
return new BeanSample();
}
// SpringTest.class
BeanSample beanSample = (BeanSample ) annotationConfigApplicationContext.getBean("beanSample");
BeanSample beanSample2 = (BeanSample ) annotationConfigApplicationContext.getBean("beanSample2");
/**
* 當@Scope("singleton")時,為false
* 當@Scope("prototype")時,為true
*/
System.out.println(beanSample == beanSample2 );
懶加載的支援需要根據具體情況進行配置。預設情況下,使用 @Bean 注解注冊的對象是在容器啟動時就初始化的,即預設是立即加載的。@Lazy隻是在單例模式下使用,讓對象需要建立的時候才注入到容器。
如果希望将其設定為懶加載,可以在 @Bean 注解上添加 @Lazy 注解。通過 @Bean 注解不僅可以注冊對象到容器中,還可以友善地設定對象的作用域和加載政策。
@ComponentScan
在最早使用Spring項目時,我們不需要将 @Controller、@Service、@Repository 這些注解添加到Spring容器中。為了将這些類加載到Spring容器中,我們通常使用了一個包掃描的配置。使用<context:component-scan base-package="com.xxx"> 這個注解來掃描指定包下的類,将其加載到Spring容器中。
@ComponentScan注解相當于 <context:component-scan> 注解,它能夠自動掃描指定包及其子包下的類,并将其注冊到Spring容器中。
添加Controller、Service、Repository
java複制代碼@Controller
public class UserController {}
@Service
public class UserService {}
@Repository
public class UserDao {}
注入到spring容器
在SpringConfig的類上添加 @ComponentScan(value = "com.xxx"),Controller、Service、Dao也注入到spring容器中了使用 @ComponentScan 注解可以友善地配置要掃描的包,并将注解類自動加載到Spring容器中。
@ComponentScan多包掃描的屬性
當你想掃描多個包的時候,你可以配置value為一個數組。
java複制代碼@ComponentScan(value = {"com.xxx.a", "com.xxx.b"})
排除某些包或者類的掃描
如果你不想讓某個包或注解被掃描到,可以在 @ComponentScan 注解中使用 excludeFilters 屬性。
在 excludeFilters 中,有幾種常見的 Filter 類型可供選擇:
- FilterType.ANNOTATION:根據注解類型進行過濾
- FilterType.ASSIGNABLE_TYPE:根據給定的類型進行過濾
- FilterType.CUSTOM:自定義過濾規則
利用這些 Filter 類型,你可以靈活地控制應該排除哪些包或注解,進而更好地管理 Spring 容器中的元件。
排除過濾:FilterType.ANNOTATION
可以使用@ComponentScan注解的excludeFilters 屬性來幫助你排除特定的注解。對于你的需求,你可以使用 FilterType.ANNOTATION 類型的過濾器,掃描 com.bearjun 包時排除 @Controller 注解。
具體的實作可以如下所示:
java複制代碼@Configuration
@ComponentScan(
basePackages = "com.xxx",
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
)
在上面的例子中,我們使用了 @ComponentScan 注解來指定需要掃描的基礎包為 com.xxx,并使用 excludeFilters 屬性來配置過濾器。在過濾器中,我們指定了類型為 FilterType.ANNOTATION,值為 Controller.class,表示要排除所有帶有 @Controller 注解的元件。
這樣配置之後,Spring 在進行元件掃描時就會排除掉 com.xxx包中帶有 @Controller 注解的元件。
排除過濾:FilterType.ASSIGNABLE_TYPE
可以使用 @ComponentScan 注解的 excludeFilters 屬性來幫助你排除特定的類。對于你的需求,你可以使用 FilterType.ASSIGNABLE_TYPE 類型的過濾器,掃描 com.xxx 包時排除 TestSample 類。
java複制代碼@Configuration
@ComponentScan(
basePackages = "com.xxx",
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = TestSample.class)
)
在上面的例子中,我們使用了 @ComponentScan 注解來指定需要掃描的基礎包為 com.xxx,并使用 excludeFilters 屬性來配置過濾器。在過濾器中,我們指定了類型為 FilterType.ASSIGNABLE_TYPE,值為 TestSample.class,表示要排除 TestSample 類。
這樣配置之後,Spring 在進行元件掃描時就會排除掉 com.xxx 包中的 TestSample 類。
排除過濾:FilterType.CUSTOM
使用 @ComponentScan 注解的 excludeFilters 屬性來幫助你根據自定義的過濾器來排除特定的元件。在這個過程中,你需要實作一個自定義的過濾器 MyFilter,然後根據過濾的邏輯傳回 true 或 false。
具體的實作可以如下所示:
java複制代碼@Configuration
@ComponentScan(value = {"com.xxx"}, excludeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, value = MyFilter.class)
})
- 下面是一個示例的自定義過濾器 MyFilter 的實作:
java複制代碼// FilterType.CUSTOM類型。
// 自定義一個類,實作 org.springframework.core.type.filter.TypeFilter;
public class MyFilter implements TypeFilter {
/**
* @param metadataReader 目前正在掃描的類 ComponentScan value的值的資訊
* @param metadataReaderFactory 擷取其他類的資訊
* @return
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
// 目前類的注解資訊
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 目前的掃描的類資訊
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 目前類資源
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
// 所有的 controller 傳回 true
if (className.toLowerCase().contains("controller")) {
return true;
}
return false;
}
}
在上述的示例中,我們使用了 @ComponentScan 注解來指定需要掃描的基礎包為 com.xxx,并使用 excludeFilters 屬性配置了一個 TYPE 類型為 CUSTOM 的過濾器。過濾器的值為自定義的 MyFilter 類。
在 MyFilter 類中,我們實作了 TypeFilter 接口,并在 match 方法中編寫了過濾的邏輯。在示例中的邏輯中,我們判斷類名是否以 "Sample" 結尾,如果是,則傳回 true,表示要過濾掉該元件。
這樣配置之後,Spring 在進行元件掃描時将掃描 com.xxx 包,但根據 MyFilter 的邏輯,滿足過濾條件的元件将被排除掉。
@Conditional
使用 @Conditional 注解可以根據一定的條件進行判斷,隻有滿足條件的情況下才會将 Bean 注入到 Spring 容器。
java複制代碼@Configuration
public class SpringConfig {
@Bean("beanSample")
BeanSample beanSample() {
return new BeanSample ();
}
@Bean("beanSample2")
@Conditional(BeanSampleCondition.class)
BeanSample beanSample2() {
return new BeanSample ();
}
}
進行實作對應的判斷邏輯條件處理方式。我們定義了 BeanSampleCondition條件類,它們實作了 Condition 接口,并重寫了 matches 方法。
java複制代碼public class BeanSampleCondition implements Condition {
/**
* @param context 上下文(可以擷取各種需要的資訊)
* @param metadata 注釋資訊
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
/**
* 擷取bean的注冊資訊,可以新增,移除,判斷。。bean
*/
BeanDefinitionRegistry registry = context.getRegistry();
/**
* 擷取項目運作的環境
*/
Environment environment = context.getEnvironment();
/**
* 擷取bean工廠
*/
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
/**
* 擷取類加載器活資源加載器
*/
ClassLoader classLoader = context.getClassLoader();
ResourceLoader resourceLoader = context.getResourceLoader();
/**
* 如果項目中是否存在一個bean叫bear
*/
return context.getRegistry().containsBeanDefinition("beanSample2");
}
}
根據系統的作業系統名稱我們可以定義我們的判斷邏輯,比如示例中判斷是否包含 "beanSample2" 命名的bean對象,如果滿足條件,則傳回 true,表示這個條件成立,對應的 Bean 将被注入到 Spring 容器。根據條件判斷,Spring 在初始化時會根據條件的結果來判斷是否将對應的 Bean 注入到 Spring 容器。
java複制代碼@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Map<String, User> beansOfType = context.getBeansOfType(User.class);
beansOfType.forEach((key, value)->{
System.out.println(key + " ==== " + value);
});
}
最後輸出的結果為:
shell複制代碼beanSample ==== BeanSample{}
beanSample2 ==== BeanSample{}
如果我們把@Bean("beanSample2")換成@Bean("beanSample3"),最後的結果為:
shell複制代碼beanSample ==== BeanSample{}
@Import
使用 @Import 注解可以引入其他配置類,并将其配置的 Bean 注冊到容器中。主要有三種方式分别是一下:
@Import +普通元件類
java複制代碼@Configuration
@ComponentScan(value = "com.bearjun")
@Import(value = {String.class, Math.class})
public class SpringConfig {
}
@Test
public void test3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beanDefinitionName = context.getBeanDefinitionNames();
for (String name : beanDefinitionName) {
System.out.println("annotation = > " + name);
}
}
結果列印存在
shell複制代碼annotation = > java.lang.String
annotation = > java.lang.Math
@Import(value="ImportSelector.class")
java複制代碼@Configuration
@ComponentScan(value = "com.bearjun")
@Import(value = {MyImportSelector.class})
public class SpringConfig {
}
public class MyImportSelector implements ImportSelector {
/**
* @param importingClassMetadata 擷取标注了目前@Import注解的是以注解資訊
* @return
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"java.lang.String"};
}
}
@Test
public void test3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beanDefinitionName = context.getBeanDefinitionNames();
for (String name : beanDefinitionName) {
System.out.println("annotation = > " + name);
}
}
結果列印存在
shell複制代碼annotation = > java.lang.String
@Import(value="ImportBeanDefinitionRegistrar.class")
java複制代碼@Configuration
@ComponentScan(value = "com.bearjun")
@Import(value = {ImportBeanDefinitionRegistrar.class})
public class SpringConfig {
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 目前類的注解資訊
* @param registry BeanDefinition注冊類
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// bean注冊器中是否存在bearjun的元件
if (registry.containsBeanDefinition("bearjun")) {
// 往元件中添加id為string的元件
BeanDefinition definition = new RootBeanDefinition("java.lang.String");
registry.registerBeanDefinition("string", definition);
}
}
}
@Test
public void test3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beanDefinitionName = context.getBeanDefinitionNames();
for (String name : beanDefinitionName) {
System.out.println("annotation = > " + name);
}
}
結果列印存在
shell複制代碼annotation = > java.lang.String
@Lookup
單例元件依賴非單例元件,可以通過調用非單例元件的方法來擷取。
- 使用@Lookup注解時,應将其放置在擷取bean的方法上才能生效。
- 從配置類中傳回的bean不能作為單例模式元件(如上述的User類),否則@Lookup注解将不會生效。
java複制代碼@Component
@Scope("prototype")
public class Car {
private String brand;
// 省略get/set/toString
}
@Component
public class User {
private Integer userId;
private String username;
private String password;
private Car car;
@Lookup
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
// SpringTest#test3
@Test
public void test3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
User user1 = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
System.out.println(user1 == user2);
// 列印:true
Car car1 = user1.getCar();
Car car2 = user2.getCar();
System.out.println(car1 == car2);
}
如果user#getCar不加@Lookup注解,列印為true,加@Lookup注解,列印為false