@Bean和@Configuration
@Configuration类似于@Component,它标明当前类时一个配置类,用于配置bean。
@Bean对应xml配置中的<bean/>标签,拥有<bean/>标签所有的属性,如果init-method等属性
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceOne();
}
}
AppConfig类等价于如下配置
<beans>
<bean id="myService" class="com.acme.services.MyServiceOne"/>
</beans>
MyServiceOne是我们上一节中定义的MyService接口的一个实现类
AnnotationConfigApplicationContext
此节之前我们都是使用的ClassPathXmlApplicationContext读取的xml配置文件进行的容器初始化。即便我们使用了基于注解的配置,也需要配置xml标签自动扫描包等。有了AnnotationConfigApplicationContext之后,我们就可以不再使用xml配置了。
package com.yyoo.boot.config;
import com.yyoo.boot.annotation.MyService;
import com.yyoo.boot.annotation.MyServiceImplOne;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppCofig1 {
@Bean
public MyService getMyService(){
return new MyServiceImplOne();
}
}
import com.yyoo.boot.config.AppCofig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo9 {
@Test
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppCofig1.class);
MyService myService = context.getBean(MyService.class);
System.out.println(myService);
myService.print();
}
}
使用AnnotationConfigApplicationContext的register方法
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();
}
使用自动组件扫描
package com.yyoo.boot.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.yyoo.boot"})
public class AppConfig2 {
}
我们在AppConfig2中没有定义Bean,通过scan扫描了com.yyoo.boot包下的所有bean,我们之前章节的代码中有MyService以及它的两个实现类都使用了@Service注解标注,在scan扫描的时候会将他们扫描到容器,容器可以直接使用。
package com.yyoo.boot.annotation;
import com.yyoo.boot.config.AppConfig2;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo10 {
@Test
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class);
MyService myService = (MyService)context.getBean("myServiceImplOne");
System.out.println(myService);
myService.print();
}
}
我们的示例getBean的时候直接通过bean的名称获取了MyServiceOne的实例,如果我们直接通过type获取的话MyService有两个满足条件的实现类,容器会报错。因为我们没有使用@Primary等注解。
如果我们不用@ComponentScan注解,也可以通过AnnotationConfigApplicationContext的scan方法定义扫描包
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.yyoo.boot");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
实际一般建议使用@ComponentScan注解定义扫描包即可。
@ComponentScan排除某些类不扫描或包含某些类需要扫描
package com.yyoo.boot.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value = {"com.yyoo.boot"},
includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @ComponentScan.Filter(Repository.class))
public class AppConfig2 {
}
其中FilterType枚举类中包含如下几类
过滤器类型 | 示例 | 说明 |
---|---|---|
ANNOTATION(默认类型) | @ComponentScan.Filter(Repository.class) | 任意的注释类 |
ASSIGNABLE_TYPE | 任意类的class | 任意的类 |
ASPECTJ | org.example…*Service+ | 目标匹配的AspectJ类型表达式 |
REGEX | org.example.Default.* | 匹配的正则表达式 |
CUSTOM | org.example.MyTypeFilter | 自定义实现的org.springframework.core.type.TypeFilter接口 |
AnnotationConfigWebApplicationContext
在web应用中,我们使用AnnotationConfigWebApplicationContext来时效性上述功能。
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
以上是官网示例,如果使用spring boot此处可以跳过。
@Bean的工作机制
我们再用之前的A、B两个类来做示例
package com.yyoo.boot.bean;
public class A {
public A(){
System.out.println("A无参构造");
}
}
package com.yyoo.boot.bean;
public class B {
private A a;
public B(){
System.out.println("B无参构造");
}
public B(A a){
this.a = a;
System.out.println("B带参构造");
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
System.out.println("B的setA方法");
}
}
@Bean示例程序
package com.yyoo.boot.config;
import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig2 {
@Bean
public A getA(){
return new A();
}
@Bean
public B getB(){
return new B(getA());
}
@Bean
public B getB1(){
return new B(getA());
}
}
package com.yyoo.boot.annotation;
import com.yyoo.boot.bean.B;
import com.yyoo.boot.config.AppConfig2;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo10 {
@Test
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class);
B b = (B)context.getBean("getB");
B b1 = (B)context.getBean("getB1");
System.out.println(b.getA());
System.out.println(b1.getA());
}
}
执行结果
14:53:05.809 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7a0ac6e3
14:53:05.899 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
14:53:06.243 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
14:53:06.248 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
14:53:06.252 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
14:53:06.255 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
14:53:06.275 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig2'
14:53:06.288 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getA'
A无参构造
14:53:06.312 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB'
B带参构造
14:53:06.313 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB1'
B带参构造
com.yyoo.boot.bean.A@1283bb96
com.yyoo.boot.bean.A@1283bb96
1、最直观的结果,A的无参构造只执行了一次。虽然我们在getB或getB1方法中都调用了方法getA,而且getA也是@Bean注解的方法,但是getA只执行了一次。因为getA也是@Bean注解的,在实例化后会被缓存起来,在getB执行的时候会先查询缓存的getA。而且两个B类的示例中a属性是一个实例。
2、我们的示例中@Bean没有定义name属性,那么其定义的bean默认名称是对应的方法名称(注意跟@Component的默认名称区别),也就是我们的示例中定义的bean名称分别为:getA、getB、getB1
所有@Configuration类在启动时都使用CGLIB. 在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存的(作用域)bean。从 Spring 3.2 开始,CGLIB 类已被重新打包org.springframework.cglib并直接包含在 spring-core JAR 中。因此我们不再需要单独引入CGLIB的包。
让我们把getA方法的@Bean注解去掉试试
14:52:07.176 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7a0ac6e3
14:52:07.228 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
14:52:07.474 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
14:52:07.478 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
14:52:07.481 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
14:52:07.484 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
14:52:07.498 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig2'
14:52:07.507 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB'
A无参构造
B带参构造
14:52:07.527 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB1'
A无参构造
B带参构造
com.yyoo.boot.bean.A@7d8995e
com.yyoo.boot.bean.A@130d63be
A的无参构造调用了两次。因为我们没有@Bean注解getA方法,那么在getB和getB1方法中调用该方法就成了普通的方法调用,所以A被调用了两次而且对应的getB和getB1的a属性不是同一个实例。
以上示例的另一种写法
package com.yyoo.boot.config;
import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig2 {
@Bean
public A getA(){
return new A();
}
@Bean
public B getB(A a){
return new B(a);
}
@Bean
public B getB1(A a){
return new B(a);
}
}
这样Spring会为两个getB方法注入getA实例
如果我们有多个A实例怎么办?
package com.yyoo.boot.config;
import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig2 {
@Bean
public A getA(){
return new A();
}
@Bean
public A getA1(){
return new A();
}
@Bean
public B getB(A a){
return new B(a);
}
@Bean
public B getB1(A a){
return new B(a);
}
}
以上写法会报错,因为在getB方法上对应的A参数实例,Spring在注入时不知道该注入getA还是getA1,此时我们需要借助@Qualifier注解
package com.yyoo.boot.config;
import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig2 {
@Bean
public A getA(){
return new A();
}
@Bean
public A getA1(){
return new A();
}
@Bean
public B getB(@Qualifier("getA")A a){
return new B(a);
}
@Bean
public B getB1(@Qualifier("getA1")A a){
return new B(a);
}
}
如果我们的getA是prototype而不是单例的会是什么结果
package com.yyoo.boot.config;
import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class AppConfig2 {
@Bean
@Scope("prototype")
public A getA(){
return new A();
}
@Bean
public B getB(A a){
return new B(a);
}
@Bean
public B getB1(A a){
return new B(a);
}
}
package com.yyoo.boot.annotation;
import com.yyoo.boot.bean.B;
import com.yyoo.boot.config.AppConfig2;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo10 {
@Test
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class);
B b = (B)context.getBean("getB");
B b1 = (B)context.getBean("getB1");
System.out.println(b.getA());
System.out.println(b1.getA());
}
}
结果如下
15:17:57.051 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7a0ac6e3
15:17:57.092 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
15:17:57.364 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
15:17:57.371 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
15:17:57.374 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
15:17:57.377 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
15:17:57.394 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig2'
15:17:57.406 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB'
A无参构造
15:17:57.438 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'getB' via factory method to bean named 'getA'
B带参构造
15:17:57.441 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB1'
A无参构造
15:17:57.441 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'getB1' via factory method to bean named 'getA'
B带参构造
com.yyoo.boot.bean.A@34a3d150
com.yyoo.boot.bean.A@2a4fb17b