前言
本文为大家介绍的是Spring容器配置相关知识,包含@Bean
和@Configuration
的使用,使用 AnnotationConfigApplicationContext
实例化Spring容器,@Bean
注解的使用,@Configuration
的使用,@Import
注解的使用,结合Java和XML配置等~
📌博主主页:小新要变强 的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
👉Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)
目录
文章标题
- 前言
- 目录
- 1️⃣@Bean和@Configuration
- 2️⃣使用 `AnnotationConfigApplicationContext`实例化Spring容器
- 3️⃣ @Bean注解
- 4️⃣`@Configuration`
- 5️⃣ 使用 `@Import `注解
- 6️⃣结合Java和XML配置
- 后记
1️⃣@Bean和@Configuration
Spring新的java配置支持的中心组件是带注解的【@Configuration
】类和带注解的【@Bean
】方法。
@Bean
注解用于指示一个方法,该方法负责【实例化、配置和初始化】一个由Spring IoC容器管理的新对象。 对于那些熟悉Spring <beans/>
XML配置的人来说,@Bean
注解扮演着与<bean/>
元素相同的角色。 你可以在任何Spring @Component
中使用@Bean
注解方法。 但是,它们最常与@Configuration
一起使用。
用@Configuration
注解的一个类表明它的主要目的是作为beanDifination的源,我们通常称之为【配置类】。 此外,【@Configuration
】类允许通过调用同一类中的其他【@Bean
】方法来【定义bean间的依赖关系】。 最简单的【@Configuration
】类如下所示:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
前面的’ AppConfig '类等价于下面的Spring <beans/>
XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
2️⃣使用 AnnotationConfigApplicationContext
实例化Spring容器
下面的章节记录了Spring 3.0中引入的【AnnotationConfigApplicationContext
】。 这个通用的【ApplicationContext
】实现不仅能够接受【@Configuration
】类作为输入,还能够接受普通的【@Component
】类和用JSR-330元数据注解的类。
当提供【@Configuration
】类作为输入时,【@Configuration
】类本身被注册为一个beanDifination,并且类中所有声明的【@Bean
】方法也被注册为beanDifination。
当提供【@Component
】和JSR-330相关的注解类时,它们被注册为beanDifination。
a、结构简洁
就像Spring XML文件在实例化【ClassPathXmlApplicationContext
】时被用作输入一样,当实例化【AnnotationConfigApplicationContext
】时,你可以使用【@Configuration
】类作为输入。 这允许Spring容器完全不使用xml,如下例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
正如前面提到的,【AnnotationConfigApplicationContext
】并不局限于只与【@Configuration
】类一起工作。 任何【@Component
】或JSR-330注解类都可以作为输入提供给构造函数,如下面的例子所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
前面的例子假设【MyServiceImpl
】、【Dependency1
】和【Dependency2
】使用Spring依赖注入注解,比如【@Autowired
】。
b、通过使用’ register(Class<?>…)'以编程方式构建容器
你可以使用一个【没有参数的构造函数】来实例化一个【AnnotationConfigApplicationContext
】,然后使用【register()
】方法来配置它。 当以编程方式构建一个“AnnotationConfigApplicationContext
”时,这种方法特别有用。 下面的例子展示了如何做到这一点:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
c、使用 scan(String…)
启用组件扫描
要启用组件扫描,你可以像下面这样注解你的 @Configuration
类:
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="com.ydlclass" / >
</beans>
同时,AnnotationConfigApplicationContext也暴露了【 scan(String…)
】方法来允许相同的组件扫描功能,如下例所示:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
请记住,【@Configuration
】类是带有【@Component
】元注解的一个注解,因此它们是组件扫描的候选对象。 在前面的例子中,假设【AppConfig
】在"com.acme
"中声明。 在’ refresh()
‘之后,它的所有’ @Bean
'方法都被处理并注册为容器中的beanDifination。
3️⃣ @Bean注解
【@Bean
】是一个方法级注解,与XML<bean/>
元素具有相同的能力。 注解支持<bean/>
提供的一些属性,例如:
- init-method
- destroy-method
- autowiring
- name
你可以在带有【@Configuration
】注解的类或带有【@Component
】注解的类中使用【@Bean
】注解。
a、声明一个 Bean
使用【@Bean
】对方法进行注解可以帮助我们申明一个bean。 您可以使用此方法在【ApplicationContext
】中注册一个beanDifination,该bean的类型会被指定为【方法的返回值类型】,而具体的返回值则是交由spring管理的bean实例。 默认情况下,bean名与方法名相同。 下面的例子显示了一个【 @Bean
】方法声明:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
上面的配置与下面的Spring XML完全相同:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
注意: 你也可以使用接口(或基类)作为返回类型来声明你的@Bean
方法,如下面的例子所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
b、Bean的依赖关系
带注解的【@Bean
】方法可以有任意数量的参数,这些参数描述构建该bean所需的依赖关系。 例如,如果我们的【TransferService
】需要一个【AccountRepository
】,我们可以用一个方法参数来实现这个依赖,如下例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
c、接受生命周期回调
- 任何用【
@Bean
】注解定义的类都支持常规的生命周期回调,并且可以使用JSR-250的’@PostConstruct
‘和’@PreDestroy
'注解。 - 也完全支持常规的Spring lifecycle回调。 如果一个bean实现了’
InitializingBean
‘、’DisposableBean
‘或’Lifecycle
',则容器会调用它们各自的方法。 - 标准的【
Aware
】接口也完全支持。
【@Bean注解
】支持指定任意的初始化和销毁回调方法,就像Spring XML在’ bean ‘元素上的’ init-method
‘和’ destroy-method
'属性一样,如下面的示例所示:
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
小知识: 对于上面例子中的’ BeanOne
‘,在构造过程中直接调用’ init()
'方法同样有效,如下例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
当您直接在代码中进行配置时,您可以对您的对象做任何您想做的事情,而不总是需要依赖于容器生命周期。
d、指定Bean范围
Spring包含了【@Scope
】注解,以便您可以指定bean的范围。
默认的作用域是’ singleton ‘,但是你可以用’ @Scope
'注解来覆盖它,如下面的例子所示:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
e、定制Bean命名
默认情况下,配置类使用【@Bean
】方法的名称作为结果bean的名称。 但是,可以使用’ name '属性覆盖该功能,如下例所示:
@Configuration
public class AppConfig {
@Bean("myThing")
public Thing thing() {
return new Thing();
}
}
有时需要为单个bean提供多个名称,或者称为bean别名。【@Bean
】注解的’ name '属性为此接受String数组。 下面的例子展示了如何为一个bean设置多个别名:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
f、Bean 描述
有时,提供bean的更详细的文本描述是很有帮助的。 当bean被公开(可能通过JMX)用于监视目的时,这可能特别有用。
要向【@Bean
】添加描述,可以使用【@Description
】注解,如下面的示例所示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
4️⃣@Configuration
【@Configuration
】是一个类级注解,指示一个对象是beanDifination的源。【@Configuration
】类通过【@Bean
】带注解的方法声明bean。 【在“@Configuration
”类上调用“@Bean
”方法也可以用来定义bean间的依赖关系】。
注入bean之间的依赖
当@Bean
方法在没有标注@Configuration
的类中声明时,它们被认为是在【lite】模式下处理的。 在【@Component
】中声明的Bean方法甚至在一个普通的类中声明的Bean方法都被认为是【lite】。在这样的场景中,【@Bean
】方法是一种通用工厂方法机制。
与@Configuration
不同,【lite】模式下 【@Bean
】方法不能【声明bean】间的【依赖关系】。 因此,这样的【@Bean
】方法不应该调用其他【@Bean
】下的方法。 每个这样的方法实际上只是特定bean引用的工厂方法,没有任何特殊的运行时语义。
在一般情况下,@Bean
方法要在【@Configuration
】类中声明,这种功能情况下,会使用【full】模式,因此交叉方法引用会被重定向到容器的生命周期管理。 这可以防止通过常规Java调用意外调用相同的Bean,这有助于减少在【lite】模式下操作时难以跟踪的微妙错误。
@Bean
和@Configuration
注解将在下面几节中深入讨论。 不过,我们首先介绍通过使用基于java的配置创建spring容器的各种方法。
当bean相互依赖时,表示这种依赖就像让一个bean方法调用另一个bean方法一样简单,如下面的示例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
// full模式可以直接调用方法,这个调用过程由容器管理,lite模式这就是普通方法调用,多次调用会产生多个实例。
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
在前面的例子中,【beanOne】通过构造函数注入接收对【beanTwo】的引用。
考虑下面的例子,它显示了一个带注解的@Bean
方法被调用两次:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
clientDao()
在【clientService1()
】和【clientService2()
】中分别被调用一次。 由于该方法创建了一个新的【ClientDaoImpl
】实例并返回它,所以通常期望有两个实例(每个服务一个)。 这肯定会有问题。在Spring中,实例化的bean默认有一个【单例】作用域,在调用父方法并创建新实例之前,首先检查容器中是否有缓存的(有作用域的)bean。
我们目前学习的描述候选组件的注解很多,但是仔细意思考,其实很简单:
我们自己的写代码通常使用以下注解来标识一个组件:
- @Component 组件的通用注解
- @Repository,持久层
- @Service,业务层
- @Controller,控制层
- @Configuration + @Bean
配置类通常是我们不能修改源代码,但是需要注入别人写的类。例如向容器注入一个德鲁伊数据源的bean,我们是绝对不能给这个类加个【@Component
】注解的。
5️⃣ 使用 @Import
注解
就像在Spring XML文件中使用<import/>
元素来实现模块化配置一样,@Import
注解允许从另一个配置类加载【@Bean
】定义,如下面的示例所示:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
现在,在实例化上下文时不需要同时指定ConfigA.class
和ConfigB.class
,只需要显式地提供【ConfigB】,如下面的示例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
这种方法简化了容器实例化,因为只需要处理一个类,而不是要求您在构造过程中记住潜在的大量【@Configuration
】类。
【小知识】: 我们一样可以给该注解传入一个实现了ImportSelector
接口的类,返回的字符串数组的Bean都会被加载到容器当中:
public class ConfigSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.ydlclass.A","com.ydlclass.B"};
}
}
6️⃣结合Java和XML配置
Spring的【@Configuration
】类支持的目标并不是100%完全替代Spring XML,有些场景xml仍然是配置容器的理想方式。
我们有如下选择:
- (1)容器实例化在一个“以XML为中心”的方式使用,例如“
ClassPathXmlApplicationContext
”。 - (2)"以java编程的方式为中心”的方式,实例化它通过使用【
@ImportResource
】注解导入XML。
以xml为中心使用“@Configuration
”类
最好从XML引导Spring容器,并以一种特别的方式包含【@Configuration
】类。将【@Configuration
】类声明为普通的Spring <bean/>
元素。记住,【@Configuration
】类最终是容器中的beanDifination。
下面的例子展示了Java中一个普通的配置类:
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
下面的例子显示了一个’ system-test-config.xml '文件的一部分:
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
下面的示例显示了一个可能的’ jdbc '。 属性的文件:
user=root
password=root
url=jdbc:mysql://127.0.0.1:3306/ydlclass?characterEncoding=utf8&serverTimezone=Asia/Shanghai
driverName=com.mysql.cj.jdbc.Driver
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
因为【@Configuration
】是用【@Component
】注解的,所以被【@Configuration
】注解的类会自动被组件扫描。 使用与前面示例中描述的相同的场景,我们可以重新定义system-test-config.xml来利用组件扫描。
下面的示例显示了修改后的system-test-config.xml文件:
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
使用@ImportResource
以类为中心使用XML
在【@Configuration
】类是配置容器的主要机制的应用程序中,可能仍然需要使用至少一些XML。 在这些场景中,您可以使用【@ImportResource
】注解,并只定义所需的XML。 这样做可以实现一种“以java为中心”的方法来配置容器,并将XML最小化。
下面的例子说明了这一点:
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml:
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties::
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
启动容器:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
后记
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~