基本LookUP:使用容器 getBean 方法
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// ApplicationContext context = new AnnotationConfigApplicationContext("com.spring.study");
// ApplicationContext context = new ClassPathXmlApplicationContext("AppConfig.xml");
Foo foo = context.getBean(Foo.class);
定义Bean
1.使用JavaConfig定义AppConfig.java
@Configuration
public class AppConfig {
@Bean
Foo foo() {
return new Foo();
}
}
2.使用XML定义
XML需要放在resources文件夹下。使用eclipse时需要把它加入build path里。IntelliJ IDEA需要把它标记为Resource文件夹。AppConfig.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="foo" class="com.spring.study.Foo"></bean>
</beans>
3.ComponentScan
可以不在容器里明示登录 Bean,选择在类声明处指定 Bean 类型注解,再使用 ComponentScan 来自动登录 Bean。
可以选择的注解有:Controller / Service / Repository / Component
Foo.java
package com.spring.study;
@Component
public class Foo {
}
AppConfig.java
@Configuration
@ComponentScan("com.spring.study")
public class AppConfig {
}
注入Bean
有3种方法。
1.Setter 注入
Foo.java
public class Foo {
public void print() {
System.out.println("This is Foo");
}
}
Bar.java
public class Bar {
private Foo foo;
public void setFoo(Foo foo) {
this.foo = foo;
}
public Foo getFoo() {
return foo;
}
}
AppConfig.java
public class AppConfig {
@Bean
Foo foo() {
return new Foo();
}
@Bean
Bar bar() {
Bar bar = new Bar();
bar.setFoo(new Foo());
return bar;
}
}
也可以直接在 Setter 方法上加入 @AutowiredFoo.java
public class Foo {
public void print() {
System.out.println("This is Foo");
}
}
Bar.java
public class Bar {
private Foo foo;
@Autowired
public void setFoo(Foo foo) {
this.foo = foo;
}
public Foo getFoo() {
return foo;
}
}
AppConfig.java
public class AppConfig {
@Bean
Foo foo() {
return new Foo();
}
@Bean
Bar bar() {
return new Bar();
}
}
使用的时候可以这样用:Application.java
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Bar bar = context.getBean(Bar.class);
bar.getFoo().print();
}
}
2.属性注入Bar.java
public class Bar {
@Autowired
private Foo foo;
}
3.构造器注入Bar.java
public class Bar {
private Foo foo;
@Autowired
public Bar(Foo foo) {
this.foo = foo;
}
}
Autowired
1.Autowired 默认按照类型进行注入。
2.如果有两个同样的类型,可以指定 @Primary 注解。
除此之外,还可以在定义 Bean 的时候指定 name 属性:@Bean(name = "foo")
。如果没有指定名字,那么方法名就是 Bean 名。
3.@Qualifier 可以和 @Autowired 组合使用来指定注入Bean的名字。
例如:
@Autowired
@Qualifier("foo")
private Foo foo
4.可以单独使用 @Resource 注解来使用 Bean 名注入。(而 @Qualifier 需要配套 @Autowired 使用)
作用域
Bean 默认是单例 (singleton) 模式,在线程不安全的环境里需要用 prototype 模式让每个 Bean 的实例都是一个独立的个体。
但是把 prototype 的 Bean 注入到 singleton 的 Bean 里,会让 prototype 失效。AppConfig.java
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
Foo foo() {
return new Foo();
}
@Bean
Bar bar() {
return new Bar(foo()); // 每次都回取得单例的 Foo
}
}
解决的办法有两种。
1.使用上下文 getBean 方法(略),或者使用 @Lookup 注解。Bar.java
@Component
public class Bar {
doSomething() {
foo();
}
@Lookup
Foo foo() {
return null; // 会被自动替换
}
}
这样一来,在上下文取得 Bar 之后,每次 foo() 方法都会取得一个 prototype 的 Foo 实例。
参考: http://stackoverflow.com/questions/26028341/how-to-use-spring-lookup-annotation
2.作用域代理(Scoped Proxy)
被设为 Proxy 模式的 Bean 实际上被注入的时候是它的一个“代理类”(而不是本身)被注入的。
这个“代理类”是 Bean 接口的一个实现,或者是它的子类。
所以代理模式有两种,作为接口实现注入的是 ScopedProxyMode.INTERFACES,作为子类注入的是 ScopedProxyMode.TARGET_CLASS。AppConfig.java
@Configuration
public class AppConfig {
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Foo foo() {
return new Foo();
}
@Bean
public Bar bar() {
return new Bar();
}
}
假设 Foo 类里有个 print() 方法。Foo.java
public class Foo {
public void print() {
System.out.println("This is Foo Class");
}
}
然后在 Bar 类里将 Foo 类注入。Bar.java
public class Bar {
@Autowired
private Foo foo;
public Foo getFoo() {
return foo;
}
}
实际注入的不是 Foo 类,而是一个 Foo 类的代理子类。
public class FooProxy extends Foo {
@Autowired
ApplicationContext context;
@Override
public void print() {
Foo foo = context.getBean(Foo.class);
foo.print();
}
}
因为 Foo 类会被重写,所以函数不能定义为 final。
在这个例子中因为 Foo 类是个 Class,不能使用 ScopedProxyMode.INTERFACES。
如果你的 Bean 是个接口,请使用 ScopedProxyMode.INTERFACES。
另外还需要注意的是,Scoped Proxy 模式下需要指定 request / session / globalSession 其中之一。虽然 prototype 也可以使用,但每执行一次函数都会生成不同的实例。
生命周期
1.生成周期
Bean 的生成周期流程是这样的:
BeanPostProcessor#postProcessBeforeInitialization > @PostConstruct > InitializingBean#afterPropertiesSet > @Bean(initMethod) > BeanPostProcessor#postProcessAfterInitialization
其中:
- @PostConstruct 和 InitializingBean#afterPropertiesSet 在各自的 Bean 类文件里定义。
- @Bean(initMethod) 在 DI 容器里定义。
- BeanPostProcessor 接口是对所有上下文初始化 Bean 做处理的时候使用,可以在 调用Bean的容器 里实现这个接口。
2.销毁周期
容器被销毁的时候会结束 Bean 的生命周期。
Bean 的销毁周期流程是这样的:
@PreDestroy > DisposableBean#destroy > @Bean(destroyMethod)