05.初始化Bean


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初始化了。既然完成了初始化,我们就可以调用所有Beaninit方法完成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方法,我们选择在工厂中编写Beaninit方法和destroy方法,并在@Bean注解中使用initMethoddestroyMethod属性记录下方法的名字。因此我们这里要支持通过方法名查看指定方法然后反射调用该方法

@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

然后我们就可以将该配置类传入提供的容器启动类来启动容器。

依赖是否正常注入

我们先对第一个点进行测试:

我们提供Bean1Bean2两个类:

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

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值