1. 自动注册Bean @Component
@Component
是类注解,用于自动检测和装配组件到 Spring 容器中。当 Spring 应用程序启动时,它通过类路径扫描,自动识别带有 @Component
注解的类,并将这些类实例化为 Spring 容器中的 beans。
使用该注解注册的Bean的名称,默认是类名的首字母小写形式。当然,也可以以注解实参的方式手动指定名称。
请注意,若要让Spring自动检测扫描组件,需要在XML中启用<context:component-scan>
标签,不同的包既可以选择以公共父包的形式,全部扫描;也可以逐一列举,以逗号分隔。
<context:component-scan base-package="path/to/your/package1,path/to/your/package2">
或者在下面的容器配置注解介绍中,使用@ComponentScan
注解等效代替。
更清晰的子注解 @Service、@Controller、@Repositry
是@Component
的子注解,作用效果与前者相同,只是拥有更清晰的说明性。
@Service
:标记服务层的组件,表明该类主要用于执行业务逻辑。@Controller
:标记控制层组件,主要用于处理 HTTP 请求。@Repository
:标记数据访问组件,主要用于封装数据库操作。
1.1 Bean作用域控制 @Scope
对于@Component
标记的Bean,可以附加@Scope
注解说明作用域。例如:
@Component
@Scope("singleton")
class Service {...}
1.2 Bean生命周期回调 @PostConsruct、@PreDestroy
Java 9已移出Java标准库
,需额外导入jakarta
,详见注解弃用说明。
用于标记Bean的初始化回调与销毁回调函数。请注意,这个使用方法与XML配置一样,@PreDestroy
标记的注册方法仍然需要关闭容器或对容器注册销毁钩子。
2. 容器配置
2.1 定义配置类 @Configuration
为了进一步简化Spring配置,我们可以使用Configuration
注解标记的配置类
完全替代applicationContext.xml
配置文件。首先来看下面这一例子:
@Configuration
@ComponentScan({"com.example.dao", "com.example.service"})
@PropertySource({"config.properties", "config.properties"})
class AppConfig {...}
配置Bean扫描 @ComponentScan
等效替代XML配置中的<context:component-scan>
读取外部配置文件 @PropertySource
请注意,使用注解方式加载的外部配置文件,无法在字符串中使用通配符*
。只能一个一个手动加载。或者采用@Bean
手动创建导入一个配置解析Bean``,以此实现对配置文件的子集,但是方法这里不做展开。对于配置项,可以在@Value
注解中通过${propname}
来使用。@Value
用于数据注入,其具体用途将在下面的章节中具体介绍。
但是这里要说明一个问题,当你使用@Value("${propname}")
注解来引用配置内容时,如果没有该配置项,那么该占位符就会单纯的被视作字符串"${propname}"
,而不会报错。这显然在一些情况下与我们的预期不符,解决方法:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
如果你想对不存在的值保持严格的控制,你应该声明一个 PropertySourcesPlaceholderConfigurer
Bean,使用上述配置可以确保在任何 ${}
占位符无法解析的情况下Spring初始化失败。请注意:当使用 JavaConfig 配置 PropertySourcesPlaceholderConfigurer
时,@Bean
方法必须是 static
的。
2.2 向容器手动注册Bean @Bean
@Bean
是一个方法级注解,其作用相当于Spring XML配置中的<bean>
标签。@Bean
标记的方法将返回的对象自动注册为Spring Bean。这个调用过程正常情况下是注解处理器完成的,一般情况下我们不需要手动调用。
默认情况下,Bean的名字和方法的名字是一样的。我们也可以通过注解形参进行修改。
一个 @Bean
注解的方法可以有任意数量的参数,描述构建该Bean所需的依赖关系。对于其所需要的其他依赖,我们可以通过以下形式提供:
引用类型
:如果引用类型是其他Bean的话,可以为该方法添加对应类型的形参,Spring容器在调用该方法时,会根据类型自动为该形参注入值。字面值
:例如创建第三方数据源实例时需要的一些配置,除了可以在该方法中以硬编码的形式写死(不推荐),还可以先在配置类中以字段的形式用@Value
注入值,再在该方法中直接使用已注入值的配置类字段。
public class DataSourceConfig {
@Value("${jdbc.driver}")
String driverClassName;
@Value("${jdbc.url}")
String url;
@Value("${jdbc.username}")
String username;
@Value("${jdbc.password}")
String password;
@Bean // @Bean返回的对象将被自动配置为Bean,且还会对参数中的依赖进行自动装配
public DataSource dataSource(UserDao userDao) {
System.out.println(userDao);
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
关于@Bean在@Configuration配置类中特性的讨论
虽然我们可以在任何Spring @Component
中使用 @Bean
注解的方法,但是, @Bean
最常被用于 @Configuration
Bean。
那么在@Configuration
和@Component
中定义@Bean
有什么区别呢?这篇博客讲的很好,可以参考。Spring @Configuration 和 @Component 区别-CSDN博客
首先,@Configuration
是@Component
的子注解,它也会作为Bean被扫描注册。但是,针对@Configuration
注解的处理器与一般组件不同。
在处理@Configuration
时,Spring使用了CGLIB动态代理增强了被注解的配置类,具体逻辑是拦截所有该类中@Bean
方法的调用,使得@Bean
方法中对该类其余@Bean
方法的调用,全部被转化为getBean
方法。也就是说,原本通过直接调用@Bean
方法创建Bean实例,而现在变成了从容器中获取已经存在的Bean,只有在不存在时才会真正去调用,这样可以确保一个@Bean
方法在同类中被多次调用时返回的是同一个实例。例如:
@Configuration
class AppConfig {
@Bean
public Foo foo() {
return new Foo();
}
@Bean
public Zoo zoo() {
return new Zoo(foo());
}
@Bean
public Dummy dummy() {
return new Dummy(foo()); // 与zoo中调用foo时,拿到的是同一个Foo Bean
}
}
而对于Component
注解,除了注解处理器自动注册@Bean
方法时注册的那个实例,此外在其他方法中对@Bean
方法的一切调用都视为普通的Java方法调用,返回的是不同实例,且不会注册为Bean。
如果希望在@Component
注解中,在@Bean
方法中描述对同一个类中其他@Bean
方法创建的Bean的依赖关系,且希望前后获取同一个实例的话,可以利用自动注入将@Bean
方法注册的Bean转化为该类内的一个成员变量,然后在其余需要该实例作为依赖的@Bean
方法中,对该变量进行引用。例:
@Configuration
class AppConfig {
@Autowired
private Foo foo; // 自动注入下面foo()方法注册的Bean
@Bean
public Foo foo() {
return new Foo();
}
@Bean
public Zoo zoo() {
return new Zoo(foo); // 通过同类
}
@Bean
public Dummy dummy() {
return new Dummy(foo; // 与zoo拿到的是同一个Foo Bean
}
}
3. 自动注入
使用@Autowired
、@Resource
注解可以实现自动注入。
3.1 @Autowired
@Autowired
注解根据注入点类型进行注入。也就是说,该注解的处理器将在容器中寻找满足注入点类型的Bean实例。下面是@Autowired
注解的定义。
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
可以看到该注解主要可以应用在如下三个点:
- 作用在构造器上
等效于XML配置的基于构造器的byType
自动注入。
public class Service {
private final UserDao userDao;
@Autowired
public MovieRecommender(UserDao userDao) {
this.userDao = userDao;
}
}
Note
:从Spring Framework 4.3开始,如果目标Bean一开始就只定义了一个构造函数,那么在这样的构造函数上就不再需要@Autowired
注解。然而,如果有几个构造函数,而且没有主要/默认构造函数,那么至少有一个构造函数必须用@Autowired
注解,以便指示容器使用哪一个。
- 作用在一般方法上
@Autowired
既可以使用XML配置中的setter注入,也可以对其它的一般方法使用该注解,效果都是基于被注解的方法对Bean中依赖进行注入。
public class Service {
private UserDao userDao;
private BookDao bookDao;
@Autowired
public void prepare(UserDao userDao,
BookDao bookDao) {
this.userDao = userDao;
this.bookDao = bookDao;
}
// ...
}
- 作用在字段上
与XML配置中的自动注入不同,当对一个成员变量使用@Autowired
时,不会要求其有可用的构造函数或setter方法。因为@Autowired
注解处理器对于字段注解的处理,是通过反射直接赋值的。
public class Service {
@Autowired
private UserDao userDao;
// ...
}
提示
:为什么不推荐使用@Autowired直接作用在字段上?
Spring官方不推荐 @Autowire使用在基于字段注入方式,推荐基于构造器注入,主要原因是:字段依赖注入容易引发 NPE 空指针异常,而构造器注入时会进行校验,若果依赖的 bean找不到就会抛出NoSuchBeanDefinitionException。
required
属性的作用
默认情况下,当一个给定的注入点没有匹配的候选Bean可用时,自动注入就会失败。默认行为是将注解的方法和字段视为表示必须的依赖关系。你可以改变这种行为,就像下面的例子所展示的那样,通过将其标记为非必需(即通过将@Autowired
中的required
属性设置为false
),使框架能够跳过一个不可满足的注入点。
public class Service {
@Autowired(required=false)
private UserDao userDao; // Null
// ...
}
注意
:任何给定的Bean类中只有一个构造函数可以在声明@Autowired
的同时将required
属性设置为true
,表示该构造函数将用作Spring Bean自动注入的首选项。如果有多个构造函数声明该注解,它们都必须声明required=false
,才能被视为自动注入的候选者(类似于XML中的autowire=constructor
)。
满足注入点类型要求的候选Bean不止一个怎么办?
因为按类型自动注入可能会导致多个候选者,所以经常需要对选择过程进行更多的控制。
1. @Primary
指定优先注入
实现这一目标的方法之一是使用Spring的 @Primary
注解。@Primary
表示,当多个Bean是自动注入到一个单值(single value)依赖的候选者时,应该优先考虑一个特定的Bean。如果在候选者中正好有一个主要(primary)Bean存在,它就会成为自动注入的值。
在下面的例子中,我们在配置类中声明了两个类型均为BookDao
的Bean,那么在其余组件使用@Autowired
进行自动注入时,将会优先选择被@Primary
标记的Bean作为注入源。
class AppConfig{
@Bean
@Primary
BookDao bookDao1() { /* ...*/ }
@Bean
BookDao bookDao2() { /* ...*/ }
}
2. @Qualifier
指定按名称注入
另一种对多个候选者进行控制的方法是使用@Qualifier
,从而找到候选者中名字符合要求的Bean。请注意,是先根据类型选择候选者,然后再在其中选择名字正确的Bean。
对于作用在字段上的@Autowired
,可以直接跟在其下方。
class Service {
@Autowired
@Qualifier("bookDao2")
BookDao bookDao; // 注入名为bookDao2的Bean
}
对于作用在方法上的@Autowired
,也可以直接修饰其函数参数。
class Service {
private BookDao bookDao; // 注入名为bookDao2的Bean
public void setBookDao(@Qualifier("bookDao2") BookDao bookDao){
this.bookDao = bookDao;
}
}
3.2 @Resource
Spring还支持通过在字段或setter方法上使用JSR-250 @Resource
注解进行自动注入。@Resource
需要一个 name
属性。默认情况下,Spring将该值解释为要注入的Bean名称。
class Service {
private BookDao bookDao; // 注入名为bookDao2的Bean
@Resource(name = "bookDao2")
public Service(BookDao bookDao){
this.bookDao = bookDao;
}
}
3.3 @Autowired与@Resource区别总结
- 来源
@Autowired
是Spring提供的注解。@Resource
是JavaEE提供的注解,只不过Spring对该注解提供了相应的注解处理器。JDK 6到8内置在javax中,而JDK 9之后,必须导入jakarta.annoration才能正常使用。
- 作用方式
@Autowired
是byType
的,可以通过@Qualifier
限定名称。@Resource
是byName
的。
- 作用范围
@Autowired
对于构造器和一般方法的处理模式不同。对于单构造器,可以不写@Autowired。@Resource
的元注解中关于方法的作用范围只有ElementType.METHOD
,而没有单独的ElementType.CONSTRUCTOR
。也就是说@Resource
无法应用于构造函数。
- 语义
@Autowired
是先按照类型获取候选,当候选项大于1个时,会抛NoUniqueBeanDefinitionException
。此时应当选择使用@Qualifer或@Primary等机制进行筛选。如果@Qualifier
提供的名字没有符合的值的话,会抛NoSuchBeanDefinitionException
。@Resource
则直接是按照名字寻找。找到的名字如果不符合类型要求,则会抛BeanNotOfRequiredTypeException
。
4. 注解弃用说明
与
@Resource
一样,@PostConstruct
和@PreDestroy
注解类型在JDK 6到8中是标准Java库的一部分。然而,整个javax.annotation
包在JDK 9中从核心Java模块中分离出来,最终在JDK 11中被删除。从Jakarta EE 9开始,该包现在住在jakarta.annotation
中。如果需要,现在需要通过Maven中心获得jakarta.annotation-api
工件,只需像其他库一样添加到应用程序的classpath中即可。 ---- Spring文档
Jakarta
[dʒəˈkɑrtə]
名称的选用:这个名字来源于 Apache Jakarta 项目,这是一个由 Apache 软件基金会创建的开源项目,用于开发多个开源的 Java 解决方案和库。Apache Jakarta 项目开始于 1999 年,由于它是 Java 开源生态中的一个重要部分,这个名称被选用来表示新的 Java EE 未来。在 2017 年,Oracle 宣布将 Java EE 的控制权转移给了 Eclipse 基金会,这是一个全球性的非盈利开源软件开发社区。随着这个转让,需要一个新的名称来表示这个平台的新时代。最终,“Jakarta EE” 被选择作为新的品牌,来继续 Java EE 的遗产,同时标志着控制权和治理结构的改变。