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("$") |
@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容器中,我们通常使用了一个包扫描的配置。使用 这个注解来扫描指定包下的类,将其加载到Spring容器中。
@ComponentScan注解相当于 注解,它能够自动扫描指定包及其子包下的类,并将其注册到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