【手撕 Spring】通过 Java 代码装配 Bean_java

👉博主介绍: 博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家,51CTO 专家博主

文章目录

  • 写在前面
  • 1、创建配置类
  • 2、声明简单的 Bean
  • 3、借助 JavaConfig 实现注入


写在前面

尽管在很多场景下通过组件扫描和自动装配实现Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,因此需要明确配置Spring。比如说,你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@component和@Autowired注解的,因此就不能使用自动化装配的方案了。

在这种情况下,你必须要采用显式装配的方式。在进行显式配置的时候,有两种可选方案:.a和XML。在这节中,我们将会学习如何使用Java配置,接下来将会继续学习Spring的XML配置。

就像我之前所说的,在进行显式配置时,JavaConig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。

同时,JavaConho与其他的Java代码又有所区别,在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他的组件一样都传用相同的语言进行表述,但JavaConho是配置代码。这意味着它不应该包含任何业务逻辑,JavaConi也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConho放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。

接下来,让我们看一下如何通过JavaConfig显式配置Spring。

1、创建配置类

package com.pany.camp.spring;

import org.springframework.context.annotation.Configuration;

/**
 *
 * @description:  配置类
 * @copyright: @Copyright (c) 2022
 * @company: Aiocloud
 * @author: pany
 * @version: 1.0.0
 * @createTime: 2023-07-28 12:40
 */
@Configuration
public class AiocloudConfig {
}

创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。

到此为止,我们都是依赖组件扫描来发现Spring应该创建的bean。尽管我们可以同时使用组件扫描和显式配置,但是在本文中,我们更加关注于显式配置,因此我将AiocloudConfig 的@ComponentScan注解移除掉了。

2、声明简单的 Bean

要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。

package com.pany.camp.spring;

/**
 *
 * @description:  接口
 * @copyright: @Copyright (c) 2022 
 * @company: Aiocloud
 * @author: pany
 * @version: 1.0.0 
 * @createTime: 2023-07-28 12:44
 */
public interface AiocloudService {
}
package com.pany.camp.spring;

import org.springframework.stereotype.Component;

/**
 *
 * @description:  实现
 * @copyright: @Copyright (c) 2022
 * @company: Aiocloud
 * @author: pany
 * @version: 1.0.0
 * @createTime: 2023-07-28 12:47
 */
@Component
public class AiocloudServiceImpl implements AiocloudService {
}
package com.pany.camp.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 *
 * @description:  配置类
 * @copyright: @Copyright (c) 2022
 * @company: Aiocloud
 * @author: pany
 * @version: 1.0.0
 * @createTime: 2023-07-28 12:40
 */
@Configuration
public class AiocloudConfig {

    @Bean
    public AiocloudService setAiocloudServiceImpl() {
        return new AiocloudServiceImpl();
    }
}

@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。

默认情况下,bean的ID与带有@Bean注解的方法名是一样的。在本例中,bean的名字将会是setAiocloudServiceImpl。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name属性指定一个不同的名字:

@Bean("aiocloudService")
public AiocloudService setAiocloudServiceImpl() {
    return new AiocloudServiceImpl();
}

不管你采用什么方法来为bean命名,bean声明都是非常简单的。方法体返回了一个新的AiocloudServiceImpl实例。这里是使用Java来进行描述的,因此我们可以发挥Java提供的所有功能,只要最终生成一个 AiocloudService 实例即可。

借助@Bean注解方法的形式,我们该如何发挥出Java的全部威力来产生bean。当你想完之后,我们要回过头来看一下在JavaConfig中,如何将 AiocloudService 注入到 AiocloudConfig 之中。

3、借助 JavaConfig 实现注入

我们前面所声明的 AiocloudService bean是非常简单的,它自身没有其他的依赖。但现在,我们需要声明 AiocloudController ,它依赖于AiocloudService 。在JavaConfig中,要如何将它们装配在一起呢?

在JavaConig中装配bean的最简单方式就是引用创建bean的方法。

package com.pany.camp.spring;

/**
 *
 * @description:  容器
 * @copyright: @Copyright (c) 2022
 * @company: Aiocloud
 * @author: pany
 * @version: 1.0.0
 * @createTime: 2023-07-28 12:55
 */
public class AiocloudController {

    private AiocloudService aiocloudService;

    public AiocloudController(AiocloudService aiocloudService) {
        this.aiocloudService = aiocloudService;

    }
}
@Bean
public AiocloudController aiocloudController() {
    return new AiocloudController(setAiocloudServiceImpl());
}

aiocloudController()方法像setAiocloudServiceImpl()方法一样,同样使用了@Bean注解,这表明这个方法会创建一个bean实例并将其注册到Spring应用上文中。所创建的beanID为cdplayer,与方法的名字相同。

aiocloudController()的方法体与setAiocloudServiceImpl() 微有些区别。在这里并没有使用默认的构造器构建实例,而是调用了需要传入AiocloudService对象的构造器来创建 AiocloudController 实例。

看起来,AiocloudService是通过调用setAiocloudServiceImpl()得到的,但情况并非完全如此。因为setAiocloudServiceImpl()方法上添加了@Bean注解,Sprin将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。

比如说,假设你引入了一个其他的它和之前的那个bean完全一样:

@Bean
public AiocloudController aiocloudController() {
    return new AiocloudController(setAiocloudServiceImpl());
}

@Bean
public AiocloudController otherAiocloudController() {
    return new AiocloudController(setAiocloudServiceImpl());
}

假如对setAiocloudServiceImpl()的调用就像其他的Java方法调用一样的话,那么每个AiocloudController 实例都会有一个自己特有的AiocloudServiceImpl实例。

但是,在软件领域中,我们完全可以将同一个AiocloudServiceImpl实例注入到任意数量的其他bean之中。默认情况下,Spring中的bean都是单例的,我们并没有必要为第二个AiocloudController bean创建完全相同的AiocloudServiceImpl实例。所以,Spring会拦截对setAiocloudServiceImpl()的调用并确保返回的是Spring所创建的bean,也就是Spring本身在调用setAiocloudServiceImpl()时所创建的AiocloudService Bean。因此,两个AiocloudController bean会得到相同的setAiocloudServiceImpl实例。

可以看到,通过调用方法来引用bean的方式有点令人困惑。其实还有一种理解起来更为简单的方式:

@Bean
public AiocloudController aiocloudController(AiocloudService  aiocloudService ) {
    return new AiocloudController(aiocloudService );
}

在这里,aiocloudController()方法请求一个AiocloudService作为参数。当Spring调用aiocloudController()创建AiocloudController bean的时候,它会自动装配一
个AiocloudService到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,aiocloudController()方法也能够将AiocloudService注入到AiocloudController的构造器中,而且不用明确引用AiocloudService的@Bean方法。

通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将AiocloudService声明到同一个配置类之中。在这里甚至没有要求AiocloudService必须要在JavaConfig中声明,实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置。你可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。不管AiocloudService是采用什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建AiocloudController bean。

另外,需要提醒的是,我们在这里使用AiocloudController的构造器实现了DI功能,但是我们完全可以采用其他风格的DI配置。比如说,如果你想通过Setter方法注入 AiocloudService 的话,那么代码看起来应该是这样的:

@Bean
public AiocloudController aiocloudController(AiocloudService  aiocloudService ) {
	AiocloudController aiocloudController = new AiocloudController(aiocloudService);
	aiocloudController.setAiocloudService(aiocloudService);
    return aiocloudController ;
}

再次强调一遍,带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。构造器和Setter方法只是@Bean方法的两个简单样例。这里所存在的可能性仅仅受到Java语言的限制。

【手撕 Spring】通过 Java 代码装配 Bean_bean装配_02