title: 05.初始化Bean
tag: 笔记 手写SSM IOC
创建Bean之后我们需要给Bean完成初始化操作
分析
在创建Bean实例的过程中,我们已经完成了强依赖的注入。下一步,是根据Setter方法和字段完成弱依赖注入,接着调用用@PostConstruct
标注的init方法,就完成了所有Bean的初始化。
这一步我们只需要找到类中标注了@Value
和@Autowired
的字段或者Setter方法进行注入即可。
除此之外,我们还需要调用Bean的初始化init方法。
总之我们这一步需要两个操作:
- 注入依赖
- 调用Bean的init方法
初始化Bean
根据以上分析我们可以创建初始化Bean的方法:
protected void initBean(){
this.beans.values().forEach(this::injectBean);//注入依赖
this.beans.values().forEach(this::callInitMethod);//调用init方法
}
注入依赖
我们这里注入依赖有两种方式:
- Setter方法注入
- 字段注入
因此我们需要拿到类的所有字段和方法并在标注了@Value
或者@Autowired
的字段或者方法来进行注入:
private void injectBean(BeanDefinition def){
Class<?> beanClass = def.getBeanClass();
try {
for (Field field : beanClass.getDeclaredFields()) {
tryInjectByField(def,field);//字段注入
}
for (Method method : beanClass.getDeclaredMethods()) {
tryInjectBySetter(def,method);//Setter方法注入
}
} catch (ReflectiveOperationException e) {
throw new BeanCreationException(e);
}
}
其中字段注入和Setter方法注入的代码逻辑非常相似,但我还是分为两个函数来进行注入:
字段(field)注入:
private void tryInjectByField(BeanDefinition def, Field field) throws ReflectiveOperationException{
Value value = field.getAnnotation(Value.class);
Autowired autowired = field.getAnnotation(Autowired.class);
if (value == null && autowired == null) return;
if (value != null && autowired != null) {
throw new BeanCreationException(String.format("Cannot specify both @Autowired and @Value when inject %s.%s for bean '%s': %s",
def.getBeanClass().getSimpleName(), field.getName(), def.getName(), def.getBeanClass().getName()));
}
checkFieldOrMethod(field);
field.setAccessible(true);
if(value != null) {
PropertyResolver pr = new PropertyResolver();
logger.atDebug().log("Field injection: {}.{} = {}",
def.getBeanClass().getName(), field.getName(), pr.getProperty(value.value(), field.getType()));
field.set(def.getInstance(), pr.getProperty(value.value(), field.getType()));
}
if(autowired != null){
boolean required = autowired.value();
Object dependentBean = getDependentBean(autowired, field.getType());
if(dependentBean == null && required) {
throw new UnsatisfiedDependencyException(String.format("Dependency bean not found when inject %s.%s for bean '%s': %s"
, def.getBeanClass().getSimpleName(),
field.getName(), def.getName(), def.getBeanClass().getName()));
}
if(dependentBean != null){
logger.atDebug().log("Field injection: {}.{} = {}",
def.getBeanClass().getName(), field.getName(), dependentBean);
field.set(def.getInstance(), dependentBean);
}
}
}
Setter注入:
private void tryInjectBySetter(BeanDefinition def, Method method) throws ReflectiveOperationException{
Value value = method.getAnnotation(Value.class);
Autowired autowired = method.getAnnotation(Autowired.class);
if (value == null && autowired == null) return;
if (value != null && autowired != null) {
throw new BeanCreationException(String.format("Cannot specify both @Autowired and @Value when inject %s.%s for bean '%s': %s",
def.getBeanClass().getSimpleName(), method.getName(), def.getName(), def.getBeanClass().getName()));
}
checkFieldOrMethod(method);
if (method.getParameters().length != 1) {
throw new BeanDefinitionException(
String.format("Cannot inject a non-setter method %s for bean '%s': %s",
method.getName(), def.getName(), def.getBeanClass().getName()));
}
method.setAccessible(true);
Class<?> injectType = method.getParameterTypes()[0];
if(value != null) {
PropertyResolver pr = new PropertyResolver();
logger.atDebug().log(" injection: {}.{} = {}",
def.getBeanClass().getName(), method.getName(), pr.getProperty(value.value(),injectType ));
method.invoke(def.getInstance(), pr.getProperty(value.value(), injectType));
}
if(autowired != null){
boolean required = autowired.value();
Object dependentBean = getDependentBean(autowired, injectType);
if(dependentBean == null && required) {
throw new UnsatisfiedDependencyException(String.format("Dependency bean not found when inject %s.%s for bean '%s': %s"
, def.getBeanClass().getSimpleName(),
method.getName(), def.getName(), def.getBeanClass().getName()));
}
if(dependentBean != null){
logger.atDebug().log("Setter injection: {}.{} = {}",
def.getBeanClass().getName(), method.getName(), dependentBean);
method.invoke(def.getInstance(), dependentBean);
}
}
}
虽然代码看起来很长,但主要逻辑就是:
- 检查注入是否合法
- 不能即
@Value
又@Autowired
- 不能注入静态(
static
)和final
的字段或者方法,Setter
方法的参数仅能有一个。
- 不能即
- 拿到注入依赖的值并通过反射注入
@Value
:通过PropertyResolver
解析值后注入@Autowired
:通过getBean()
,拿到依赖的Bean
后注入。
现在我们就完成依赖的注入,将所有的Bean
初始化了。既然完成了初始化,我们就可以调用所有Bean
的init
方法完成Bean
的初始化阶段了。
调用init方法
因为除了init
方法,bean
还有销毁destroy
方法,为了复用,我们再定义一个callMethod
方法:
private void callInitMethod(BeanDefinition definition){
callMethod(definition.getInstance(), definition.getInitMethod(), definition.getInitMethodName());
}
callMethod
:
private void callMethod(Object instance, Method Method, String MethodName) {
if(Method != null){
try {
Method.invoke(instance);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new BeanCreationException(e);
}
}
if(MethodName != null && !MethodName.isEmpty()){
try {
Method initmethod = instance.getClass().getDeclaredMethod(MethodName);
initmethod.invoke(instance);
} catch (ReflectiveOperationException e) {
throw new BeanCreationException(e);
}
}
}
注意:
由于由BeanFactory
创建的Bean
无法使用注解标注init
方法或者destroy
方法,我们选择在工厂中编写Bean
的init
方法和destroy
方法,并在@Bean
注解中使用initMethod
和destroyMethod
属性记录下方法的名字。因此我们这里要支持通过方法名查看指定方法然后反射调用该方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
/**
* Bean name. default to method name.
*/
String value() default "";
String initMethod() default "";
String destroyMethod() default "";
}
测试
我们编写一个程序测试以下几点:
- 依赖是否正常注入
- 容器中存在同一个接口的多个实现,是否能够使用指定名字或者@Primary注解来拿到正确的Bean
- 正常的Bean的init方法和Factory创建的Bean的init方法是否正常调用
我们首先提供一个注解配置类来提供启动容器需要的信息:
@Configuration
@ComponentScan
@PropertySource("jdbc.properties")
public class config {
}
@ComponentScan
没有给值,因此默认扫描config所在的包。@PropertySource
用于传入配置信息
这里我们模拟datasource
的配置:
jdbc.properties
:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_db?useSSL=false
jdbc.username=root
jdbc.password=123456789a
然后我们就可以将该配置类传入提供的容器启动类来启动容器。
依赖是否正常注入
我们先对第一个点进行测试:
我们提供Bean1
和Bean2
两个类:
Bean1
:
@Component
public class Bean1 {
@Value("段")
String name;
@Value("${jdbc.username}")
String username;
public void print(){
System.out.println("我是bean1");
}
@PostConstruct
public void init(){
System.out.println("bean1初始化完成");
}
}
在该类中,我们顺便测试了@Value的注入,并定义了一个init方法。
Bean2:
@Component
public class Bean2 {
Bean1 bean1;
public void useBean1(){
bean1.print();
System.out.println(bean1.name);
System.out.println(bean1.username);
}
@Autowired
public void setBean1(Bean1 bean1){
this.bean1 = bean1;
}
@PostConstruct
public void init(){
System.out.println("bean2初始化完成");
}
}
Bean2
类使用Setter
方法注入了Bean1
,并在useBean1
方法中调用了Bean的方法和属性。同样,我们也定义了一个init方法。
现在我们就可以编写程序测试注入是否正常了:
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config.class)) {
Bean2 bean2 = context.getBean(Bean2.class);
bean2.useBean1();
}
输出:
bean1初始化完成
bean2初始化完成
我是bean1
段志宇
root
可以看到,输出均符合我们的预期。
测试同一个接口多个实现的注入
该测试也就是在按类型注入时,有多个匹配的Bean
,若没有@Primary
注解或者使用名字标识需要注入的Bean
,我们会抛出错误,因为我们无法判断需要注入哪个实现类。
我们想要测试在这样的情况下,我们的容器是否能够正常注入。
这里我们模拟MVC架构中,Service
有两个实现类,在Controller
中注入Service
。
public interface iService {
void print();
}
@Component()
public class ServiceImpl1 implements iService{
@Override
public void print() {
System.out.println("我是ServiceImpl1");
}
}
@Component()
public class ServiceImpl2 implements iService{
@Override
public void print() {
System.out.println("我是ServiceImpl2");
}
}
这里我们写了两个实现了同一个接口的类。
@Controller
public class IController {
@Autowired()
iService service;
public void userService(){
service.print();
}
}
我们在Controller
中按照Service类型去注入,此时在容器中会存在两个匹配的Bean并且没有标识,因此现在启动容器应当会抛出错误。
输出:
com.duan.summer.exception.NoUniqueBeanDefinitionException: Multiple bean with type 'testInjectBean.iService' found, but no @Primary specified.
如我们预料,启动容器时抛出了NoUniqueBeanDefinitionException
错误。
我们可以通过两种方式去使Controller正常注入:
- 使用
@Primary
标注需要注入的实现类 - 不使用类型自动注入而是为Bean取名使用具体实现类的名字来实现注入
总之,我们需要让容器知道我们需要注入的是哪一个实现类。
下面我门使用@Primary
标注Service1来测试是否能够正常注入:
@Component()
@Primary
public class ServiceImpl1 implements iService{
@Override
public void print() {
System.out.println("我是ServiceImpl1");
}
}
编写测试:
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config.class)) {
IController controller = context.getBean(IController.class);
controller.userService();
}
输出:
我是ServiceImpl1
容器正常运行,并且在Controller
中注入了正确的ServiceImpl1
ride
public void print() {
System.out.println(“我是ServiceImpl1”);
}
}
编写测试:
```java
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config.class)) {
IController controller = context.getBean(IController.class);
controller.userService();
}
输出:
我是ServiceImpl1
容器正常运行,并且在Controller
中注入了正确的ServiceImpl1