JUN

Spring 学习笔记 : DI 容器篇
基本LookUP:使用容器 getBean 方法ApplicationContext context = new ...
扫描右侧二维码阅读全文
06
2016/12

Spring 学习笔记 : DI 容器篇

基本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 方法上加入 @Autowired
Foo.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)

Last modification:December 8th, 2016 at 02:42 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment