关于springboot:SpringBean的注册与注入方式

58次阅读

共计 8756 个字符,预计需要花费 22 分钟才能阅读完成。

1、背景

IoC(Inversion of Control)管制反转,IoC 是一种通过形容来生成或获取对象的技术。每一个须要治理的对象称为 Spring Bean,而 Spring 治理这些 Bean 的容器称为 Spring IoC 容器,也就是咱们所说的 Spring 应用程序上下文 ApplicationContext。

IOC 容器负责实例化、定位、配置应用程序中的对象及建设这些对象间的依赖。交由 Spring 容器对立进行治理,从而实现松耦合。

  1. 容器启动,创立和初始化 IoC 容器
  2. 扫描包下所有 class,通过反射解析 class 类的信息,包含注解信息
  3. 基于反射实例的对象,对其进行封装
  4. 将实例的对象放入汇合中保留
  5. 能够通过 getBean 获取汇合中对象

2、@SpringBootApplication

@SpringBootApplication 开启了 Spring 的组件扫描和 SpringBoot 主动配置性能。实际上它是一个复合注解,蕴含 3 个重要的注解:@SpringBootConfiguration、@ComponentScan、@EnableAutoConfiguration

SpringBoot 晚期的版本中,须要在入口类同时增加这 3 个注解,但从 SpringBoot1.2.0 开始,只有在入口类增加 @SpringBootApplication 注解即可。咱们如果想了解它的含意,能够从通过剖析这 3 个注解开始。

2.1、@SpringBootConfiguration

@SpringBootConfiguration 是对 @Configuration 的简略封装,二者性能上没有太大差别。后者咱们比拟相熟,所以 @SpringBootConfiguration 能够了解为配置类的注解,会将以后类内申明的一个或多个以 @Bean 注解标记的办法的实例纳入到 spring 容器中。

2.2、@ComponentScan

@ComponentScan 次要就是定义扫描的门路,从中找出标识了须要拆卸的类主动拆卸到 spring 的 bean 容器中。默认扫描门路是以后门路,因为启动类是在我的项目的根门路,所以启动类中的该注解,默认扫描的以后我的项目门路。

那么怎么找出须要拆卸的类呢?咱们晓得失常装载类,是要在配置类下注册 Bean 的。然而咱们日常应用的 @Controller、@Service、@Repository、@Component、@Configuration 却能够主动装载,就是因为 @ComponentScan 这个注解。@Component 是个非凡的注解,它能够被 @ComponentScan 扫描装载,如果查看 @Controller、@Service、@Repository、@Configuration 等注解的源码就会发现,它们都继承 @Component,因而同样也能够主动被扫描装载进容器中。

@ComponentScan 注解的罕用属性包含:

  • value/basePackages:定义扫描的门路。
  • includeFilters:退出扫描门路下,没有继承 @Component 注解的类退出 spring 容器。
  • excludeFilters:过滤出不必退出 spring 容器的类。

2.3、@EnableAutoConfiguration

我的项目开发时,常常通过 maven 等形式引入第三方 jar 包,那么也同样冀望可能将第三方 jar 包中的 Bean 也加载进以后我的项目的 Bean 容器中。后面说过 @ComponentScan 能够将定义扫描门路下的 Bean 装载进容器中,因而能够手动的在 @ComponentScan 注解的 value 属性中,填上所有第三方 jar 包的扫描门路。实践上是这样的,但应该没有人这么干吧?一个我的项目依赖几十个 jar 包,那要配置多少扫描门路?

因而个别有另外的两种办法来实现需求,个别咱们开发一个供调用的 jar 包利用(如 starter),次要依靠自身的配置类来注入 Bean(@Configuration+@Bean 或 @ComponentScan+@Component),因而只有保障第三方利用的配置类能加载进以后的 Bean 容器即可。

@Import 和 spring.factories

  1. 第三方利用,定义一个注解(个别是 Enable 结尾),通过 @Import 注解注入配置类。并且以后我的项目的配置类(倡议启动类)中加上该注解。
// 配置类
@Configuration
@EnableConfigurationProperties({SecurityProperty.class})
@ComponentScan(basePackages = {"com.smec.mpaas.unicorn"})
public class UnicornAutoConfig { }
// 注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Import({UnicornAutoConfig.class})
public @interface EnableUnicorn {}
  1. 在第三方利用 /META-INF 目录下创立 spring.factories 文件,并定义须要加载的配置类门路。以后我的项目的 @EnableAutoConfiguration
// mybatis 的 spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

咱们重点谈谈第二种,因为用到了 @EnableAutoConfiguration,注解的定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class[] exclude() default {};
    String[] excludeName() default {};}

@EnableAutoConfiguration 实现的关键在于引入了 AutoConfigurationImportSelector,其外围逻辑为 selectImports 办法,借助 AutoConfigurationImportSelector,它能够帮忙 SpringBoot 利用将所有符合条件的 @Configuration 配置都加载到以后 SpringBoot 创立并应用的 IoC 容器。

xxxAutoConfiguration 条件注解

当 springboot 扫描到 @EnableAutoConfiguration 注解时则会将 spring-boot-autoconfigure.jar/META-INF/spring.factories 文件中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的 value 里的所有 xxxConfiguration 类加载到 IOC 容器中。spring.factories 文件里每一个 xxxAutoConfiguration 文件个别都会有上面的条件注解:

  • @ConditionalOnClass:classpath 中存在该类时起效
  • @ConditionalOnMissingClass:classpath 中不存在该类时起效
  • @ConditionalOnBean:DI 容器中存在该类型 Bean 时起效
  • @ConditionalOnMissingBean:DI 容器中不存在该类型 Bean 时起效
  • @ConditionalOnSingleCandidate:DI 容器中该类型 Bean 只有一个或 @Primary 的只有一个时起效
  • @ConditionalOnExpression:SpEL 表达式后果为 true 时
  • @ConditionalOnProperty:参数设置或者值统一时起效
  • @ConditionalOnResource:指定的文件存在时起效
  • @ConditionalOnJndi:指定的 JNDI 存在时起效
  • @ConditionalOnJava:指定的 Java 版本存在时起效
  • @ConditionalOnWebApplication:Web 应用环境下起效
  • @ConditionalOnNotWebApplication:非 Web 应用环境下起效

AutoConfigurationImportSelector 类的逻辑

SpringBoot 中 EnableAutoConfiguration 实现的关键在于引入了AutoConfigurationImportSelector,其外围逻辑为 selectImports 办法,逻辑大抵如下:

  1. 从配置文件 META-INF/spring.factories 加载所有可能用到的主动配置类;
  2. 去重,并将 exclude 和 excludeName 属性携带的类排除;
  3. 过滤,将满足条件(@Conditional)的主动配置类返回;

3、Bean 注册

IoC 容器中次要就是治理 Bean,那么在这之前,最重要的就是如何将自定义的 Bean 注册进 IoC 容器中。常见的有两种形式:(1)配置 @Component 注解,或者蕴含 @Component 的注解;(2)@Bean+@Configuration 的组合。

3.1、@Component

@Component 偏向于组件扫描和主动拆卸。前文说过了,次要是配合 @ComponentScan 注解,该注解会扫描指定门路下蕴含 @Component 注解的类,并将其注册进 IoC 容器。spring 中有很多其余的注解也是依靠 @Component 实现的,如:@Controller、@Service、@Repository、@Configuration 等。

注册进 IoC 容器后,Bean 的名字取决于两种状况:

  1. 如果在注解中指定了 value,则为 value 的值
  2. 如果在注解中没有指定 value,默认是类名的第一个字母小写命名(如:类名 UserDepartMerge,bean 名 userDepartMerge)

@Component 只有一个注解属性:

  • value:在代码中定义了别名,为 bean 起一个名字。

3.2、@Bean

@Bean 润饰的办法,必须中配置类有 @Configuration 的类)中定义。在办法上打上注解 @Bean,办法返回咱们要创立的对象,即示意申明该办法返回的实例是受 Spring 治理的 Bean。

相比拟于 @Component 而言,@Bean 的形式更加灵便。如果你要引入第三方库,可是如果你没有源代码,也就无奈在其上增加 @Component,主动设置也就无从下手,但 @Bean 会返回一个被 spring 认可的 Bean。

@Configuration
public class BeanConfig {@Bean(initMethod = "initMethod",destroyMethod = "destroyMethod")
    public User user(){return new User();
    }
}

注册进 IoC 容器后,Bean 的名字取决于两种状况:

  1. 如果在注解中指定了 name,则为 name 属性的值
  2. 如果没有指定 name,默认是办法名(示例中即:user)

@Bean 的注解属性解析:

  • value/name:name 和 value 两个属性是雷同的含意的,在代码中定义了别名。为 bean 起一个名字,如果默认没有写该属性,那么就应用办法的名称为该 bean 的名称。
  • autowire:拆卸形式 有三个选项 Autowire.NO (默认设置)/Autowire.BY_NAME/Autowire.BY_TYPE。指定 bean 的拆卸形式,依据名称和依据类型拆卸,个别不设置,采纳默认即可。
  • initMethod:bean 的初始化办法,间接指定办法名称(bean 所对应类中,某个实例办法的名称)即可,不必带括号。
  • destroyMethod:bean 的销毁办法,在调用 IoC 容器的 close()办法时,会执行到该属性指定的办法。不过,只是单实例的 bean 才会调用该办法,如果是多实例的状况下,不会调用该办法。

4、Bean 注入

bean 的齐全初始化过程比较复杂,简略来说:先通过构造方法生成一个实例,其次再初始化实例中属性的值。当 bean 曾经被加载进 IoC 容器后,在有些开发过程中如果须要用到一些 bean 的实例,就要对其注入。
spring 倒退中,风行过三种注入形式,当有多个 bean 注入时,如果存在依赖关系,“谁先加载,谁后加载”有时就很重要。这和上面三种注入形式也有关系,一个经典的案例就是 spring 的循环依赖问题,这个前面会有独自一篇文章介绍,下文先打好根底。

4.1、基于字段注入

这是晚期最常见的注入形式,借助 @Autowired 或 @Resource 来注入,注解能够加在字段上。

byName 和 byType

spring 在获取 bean 时,有两种依赖路径:byName 和 byType。之前在介绍 bean 的注册时,始终强调 bean 被加载进 IoC 容器后命名,byName 就是依据 bean 的名字去 IoC 容器中寻找 bean 注入。而 byType 是依据被注册 bean 的类型来寻找的,类型即 bean 对应的类或接口。

@Resource 注解

@Resource 注解的门路是 javax.annotation;,它是属于 J2EE 的注解。
@Resource 默认是依照 byName 形式注入的。
@Resource 有两个重要属性:name,type:

  • 如果只指定了 name 属性,则依照 byName 形式注入;
  • 如果只指定了 type 属性,则依照 byType 形式注入;
  • 如果两个属性都没指定,则默认依照 byName 形式注入;
  • 如果两个属性都指定了,则要求依照 byNamebyType两种形式都匹配,才会注入。

示例代码:

@Component
public class User {@Resource(name = "type",type = Type.class)
    private Type type;

}

@Autowirec 注解

@Autowired 注解的门路是 org.springframework.beans.factory.annotation;,它是属于Spring 的注解。
@Autowired 并不能指定注入形式是 byName 还是 byType,默认只能依照 byType 形式注入。如果想通过 byName 形式注入,得配合 @Qualifier 注解一起应用:

  • 默认依照 byType 形式注入;
  • 配合 @Qualifier 注解应用,能够实现 byName 形式注入;
  • 如果依照 byType 匹配到多个 bean,则会主动将 属性名 视为 bean 的名称,通过 byName 形式匹配注入。

示例代码中,有个 Action 的接口,两个实现类 Human、Dog。因为 Action 类型能匹配两个 bean,依据属性名 dog 会匹配到 dog:

//Action
@Component
public interface Action {String run();
}
//Human
@Component
public class Human implements Action{
    @Override
    public String run() {return "Human run!";}
}
//Dog
@Component
public class Dog implements Action{
    @Override
    public String run() {return "Dog run!";}
}
//Controller
@RestController
public class DemoController {
    @Autowired
    private Action dog;

    @GetMapping("/demo")
    public String demo() {return dog.run();
    }
}

4.2、基于 setter 注入

基于 setter 的注入,就是增加属性的 setter 办法。setter 办法上的 @Autowired 注解能够加,也能够不加,不会影响性能实现,加了可能只是有助于辨别其余一般的 setter 办法。

@Component
public class User {
    private Type type;

    //@Autowired
    public void setType(Type type) {this.type = type;}
}

上文代码中,User 和 Type 两个类都加上了 @Component 的注解,即都曾经被注入 IoC 容器中。User 在创立实例的时候只执行本身的构造方法即可,但在初始化 type 属性数据时会调用 set 办法,会注入 Type 类的实例。所以会先执行 User 构造方法,再执行 Type 构造方法。

相较于 字段注入 的形式来说,改善了局部问题,但不显著。而且如果须要注入的 bean 多,settter 办法就要写一堆,代码构造很蹩脚。所以个别更举荐 结构器注入

4.3、结构器注入

下列代码是不是很相熟?Angular 中依赖注入组件,就是这种写法。在 User 创立实例时,就依赖于 Type 的实例。因而先执行 Type 的构造方法,再执行 User 的构造方法。

@Component
public class User {
    private final Type type;

    public User(Type type) {this.type = type;}
}

这个结构器注入的形式啊,可能保障注入的组件不可变(final),并且确保须要的依赖不为空。此外,结构器注入的依赖总是可能在返回客户端(组件)代码的时候保障齐全初始化的状态。

4.4、比拟

基于 字段注入 的形式,会使代码看起来更整洁,然而对于空指针等问题无奈及时防止。基于 结构器注入 的形式,貌似是前者短板的最好代替。我不倡议思考 setter 注入形式。

咱们当在字段上应用 @Autowired 时,IDE 个别都会给咱们一个正告 Field injection is not recommended,不举荐应用字段注入。这个提醒是 spring framerwork 4.0 当前开始呈现的,spring 4.0 开始就不举荐应用属性注入,改为举荐 结构器注入 setter 注入

@Autowired 因为默认是 byType 注入,常常会因为注入的类型是接口,在初始化属性数据时,却发现 IoC 容器中并没有加载实现类。而后就呈现了空指针的谬误,包含还有可能暗藏的循环依赖谬误。因而在很多的开发标准中,都举荐 用结构器注入,代替 @Autowired

但为啥对于在字段上应用 @Resource,IDE 不正告呢?集体认为毕竟 @Resource 的属性很多,能够定制化实现很多性能,这些灵便的性能,结构器注入形式代替不了。不像 @Autowired 那么鸡肋。

5、Aware 属性注入

Spring 中有很多继承于 aware 中的接口, 上面列出了一部分:

  • ApplicationContextAware
  • ApplicationEventPublisherAware
  • BeanClassLoaderAware
  • BeanFactoryAware
  • BeanNameAware
  • EnvironmentAware
  • ImportAware
  • MessageSourceAware
  • NotificationPublisherAware
  • ResourceLoaderAware
  • ServletConfigAware
  • ServletContextAware

aware,英文翻译是“晓得的,意识到的”。能够了解成,如果实现了这些 xxxAware的接口,就能感知到被润饰的 xxx 属性。如:实现 BeanNameAware 接口,能获取到以后 bean 的名称。实现 ApplicationContextAware 接口,能获取到 ApplicationContex。

这些接口中都有且只有一个去掉接口名中的 Aware 后缀的设置办法,例如 BeanNameAware 接口只有一个 void setBeanName(String var1) 的办法。

所以能够了解成,这些 Aware 接口的作用,是让开发者实现相应的 属性注入。如下例中,通过单例模式实现一个获取全局 ApplicationContext 的办法,能够手动获取 IoC 容器和容器中的 bean。

@Component
public class ApplicationContextAwareImpl implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext curApplicationContext) {if (applicationContext == null) {applicationContext = curApplicationContext;}
    }

    public static ApplicationContext getApplicationContext() {return applicationContext;}

}

正文完
 0