前言


在宏大的java体系中,spring有着无足轻重的位置,它给每位开发者带来了极大的便当和惊喜。咱们都晓得spring是创立和治理bean的工厂,它提供了多种定义bean的形式,可能满足咱们日常工作中的多种业务场景。

那么问题来了,你晓得spring中有哪些形式能够定义bean?

我预计很多人会说出以下三种:

最近无意间取得一份BAT大厂大佬写的刷题笔记,一下子买通了我的任督二脉,越来越感觉算法没有设想中那么难了。
BAT大佬写的刷题笔记,让我offer拿到手软

没错,但我想说的是以上三种形式只是开胃小菜,实际上spring的性能远比你设想中更弱小。

各位看官如果不信,请持续往下看。

1. xml文件配置bean

咱们先从xml配置bean开始,它是spring最早反对的形式。起初,随着springboot越来越受欢迎,该办法目前曾经用得很少了,但我倡议咱们还是有必要理解一下。

1.1 结构器

如果你之前有在bean.xml文件中配置过bean的经验,那么对如下的配置必定不会生疏:

<bean id="personService" class="com.sue.cache.service.test7.PersonService"></bean>

这种形式是以前应用最多的形式,它默认应用了无参结构器创立bean。

当然咱们还能够应用有参的结构器,通过<constructor-arg>标签来实现配置。

<bean id="personService" class="com.sue.cache.service.test7.PersonService">   <constructor-arg index="0" value="susan"></constructor-arg>   <constructor-arg index="1" ref="baseInfo"></constructor-arg></bean>

其中:

  • index示意下标,从0开始。
  • value示意常量值
  • ref示意援用另一个bean

1.2 setter办法

除此之外,spring还提供了另外一种思路:通过setter办法设置bean所需参数,这种形式耦合性绝对较低,比有参结构器应用更为宽泛。

先定义Person实体:

@Datapublic class Person {    private String name;    private int age;}

它外面蕴含:成员变量name和age,getter/setter办法。

而后在bean.xml文件中配置bean时,加上<property>标签设置bean所需参数。

<bean id="person" class="com.sue.cache.service.test7.Person">   <property name="name" value="susan"></constructor-arg>   <property name="age" value="18"></constructor-arg></bean>

1.3 动态工厂

这种形式的要害是须要定义一个工厂类,它外面蕴含一个创立bean的静态方法。例如:

public class SusanBeanFactory {    public static Person createPerson(String name, int age) {        return new Person(name, age);    }}

接下来定义Person类如下:

@AllArgsConstructor@NoArgsConstructor@Datapublic class Person {    private String name;    private int age;}

它外面蕴含:成员变量name和age,getter/setter办法,无参结构器和全参结构器。

而后在bean.xml文件中配置bean时,通过factory-method参数指定动态工厂办法,同时通过<constructor-arg>设置相干参数。

<bean class="com.sue.cache.service.test7.SusanBeanFactory" factory-method="createPerson">   <constructor-arg index="0" value="susan"></constructor-arg>   <constructor-arg index="1" value="18"></constructor-arg></bean>

1.4 实例工厂办法

这种形式也须要定义一个工厂类,但外面蕴含非动态的创立bean的办法。

public class SusanBeanFactory {    public Person createPerson(String name, int age) {        return new Person(name, age);    }}

Person类跟下面一样,就不多说了。

而后bean.xml文件中配置bean时,须要先配置工厂bean。而后在配置实例bean时,通过factory-bean参数指定该工厂bean的援用。

<bean id="susanBeanFactory" class="com.sue.cache.service.test7.SusanBeanFactory"></bean><bean factory-bean="susanBeanFactory" factory-method="createPerson">   <constructor-arg index="0" value="susan"></constructor-arg>   <constructor-arg index="1" value="18"></constructor-arg></bean>

1.5 FactoryBean

不晓得大家有没有发现,下面的实例工厂办法每次都须要创立一个工厂类,不方面对立治理。

这时咱们能够应用FactoryBean接口。

public class UserFactoryBean implements FactoryBean<User> {    @Override    public User getObject() throws Exception {        return new User();    }    @Override    public Class<?> getObjectType() {        return User.class;    }}

在它的getObject办法中能够实现咱们本人的逻辑创建对象,并且在getObjectType办法中咱们能够定义对象的类型。

而后在bean.xml文件中配置bean时,只需像一般的bean一样配置即可。

<bean id="userFactoryBean" class="com.sue.async.service.UserFactoryBean"></bean>

轻松搞定,so easy。

留神:getBean("userFactoryBean");获取的是getObject办法中返回的对象。而getBean("&userFactoryBean");获取的才是真正的UserFactoryBean对象。

咱们通过下面五种形式,在bean.xml文件中把bean配置好之后,spring就会主动扫描和解析相应的标签,并且帮咱们创立和实例化bean,而后放入spring容器中。

虽说基于xml文件的形式配置bean,简略而且非常灵活,比拟适宜一些小我的项目。但如果遇到比较复杂的我的项目,则须要配置大量的bean,而且bean之间的关系盘根错节,这样长此以往会导致xml文件迅速收缩,十分不利于bean的治理。

2. Component注解

为了解决bean太多时,xml文件过大,从而导致收缩不好保护的问题。在spring2.5中开始反对:@Component@Repository@Service@Controller等注解定义bean。

如果你有看过这些注解的源码的话,就会惊奇得发现:其实后三种注解也是@Component

@Component系列注解的呈现,给咱们带来了极大的便当。咱们不须要像以前那样在bean.xml文件中配置bean了,当初只用在类上加Component、Repository、Service、Controller,这四种注解中的任意一种,就能轻松实现bean的定义。

@Servicepublic class PersonService {    public String get() {        return "data";    }}

其实,这四种注解在性能上没有特地的区别,不过在业界有个不成文的约定:

  • Controller 个别用在管制层
  • Service 个别用在业务层
  • Repository 个别用在数据层
  • Component 个别用在公共组件上

太棒了,几乎一下子解放了咱们的双手。

不过,须要特地留神的是,通过这种@Component扫描注解的形式定义bean的前提是:须要先配置扫描门路

目前罕用的配置扫描门路的形式如下:

  1. 在applicationContext.xml文件中应用<context:component-scan>标签。例如:
<context:component-scan base-package="com.sue.cache" />
  1. 在springboot的启动类上加上@ComponentScan注解,例如:
@ComponentScan(basePackages = "com.sue.cache")@SpringBootApplicationpublic class Application {    public static void main(String[] args) {        new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);    }}
  1. 间接在SpringBootApplication注解上加,它反对ComponentScan性能:
@SpringBootApplication(scanBasePackages = "com.sue.cache")public class Application {        public static void main(String[] args) {        new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);    }}

当然,如果你须要扫描的类跟springboot的入口类,在同一级或者子级的包上面,无需指定scanBasePackages参数,spring默认会从入口类的同一级或者子级的包去找。

@SpringBootApplicationpublic class Application {        public static void main(String[] args) {        new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);    }}

此外,除了上述四种@Component注解之外,springboot还减少了@RestController注解,它是一种非凡的@Controller注解,所以也是@Component注解。

@RestController还反对@ResponseBody注解的性能,行将接口响应数据的格局主动转换成json。

@Component系列注解曾经让咱们爱不释手了,它目前是咱们日常工作中最多的定义bean的形式。

3. JavaConfig

@Component系列注解虽说应用起来十分不便,然而bean的创立过程齐全交给spring容器来实现,咱们没方法本人管制。

spring从3.0当前,开始反对JavaConfig的形式定义bean。它能够看做spring的配置文件,但并非真正的配置文件,咱们须要通过编码java代码的形式创立bean。例如:

@Configurationpublic class MyConfiguration {    @Bean    public Person person() {        return new Person();    }}

在JavaConfig类上加@Configuration注解,相当于配置了<beans>标签。而在办法上加@Bean注解,相当于配置了<bean>标签。

此外,springboot还引入了一些列的@Conditional注解,用来管制bean的创立。

@Configurationpublic class MyConfiguration {    @ConditionalOnClass(Country.class)    @Bean    public Person person() {        return new Person();    }}

@ConditionalOnClass注解的性能是当我的项目中存在Country类时,才实例化Person类。换句话说就是,如果我的项目中不存在Country类,就不实例化Person类。

这个性能十分有用,相当于一个开关管制着Person类,只有满足肯定条件能力实例化。

spring中应用比拟多的Conditional还有:

  • ConditionalOnBean
  • ConditionalOnProperty
  • ConditionalOnMissingClass
  • ConditionalOnMissingBean
  • ConditionalOnWebApplication

如果你对这些性能比拟感兴趣,能够看看《spring中那些让你爱不释手的代码技巧(续集)》,这是我之前写的一篇文章,外面做了更具体的介绍。

上面用一张图整体认识一下@Conditional家族:

nice,有了这些性能,咱们终于能够辞别麻烦的xml时代了。

4. Import注解

通过后面介绍的@Configuration和@Bean相结合的形式,咱们能够通过代码定义bean。但这种形式有肯定的局限性,它只能创立该类中定义的bean实例,不能创立其余类的bean实例,如果咱们想创立其余类的bean实例该怎么办呢?

这时能够应用@Import注解导入。

4.1 一般类

spring4.2之后@Import注解能够实例化一般类的bean实例。例如:

先定义了Role类:

@Datapublic class Role {    private Long id;    private String name;}

接下来应用@Import注解导入Role类:

@Import(Role.class)@Configurationpublic class MyConfig {}

而后在调用的中央通过@Autowired注解注入所需的bean。

@RequestMapping("/")@RestControllerpublic class TestController {    @Autowired    private Role role;    @GetMapping("/test")    public String test() {        System.out.println(role);        return "test";    }}

聪慧的你可能会发现,我没有在任何中央定义过Role的bean,但spring却能主动创立该类的bean实例,这是为什么呢?

这兴许正是@Import注解的弱小之处。

此时,有些敌人可能会问:@Import注解能定义单个类的bean,但如果有多个类须要定义bean该怎么办呢?

祝贺你,这是个好问题,因为@Import注解也反对。

@Import({Role.class, User.class})@Configurationpublic class MyConfig {}

甚至,如果你想偷懒,不想写这种MyConfig类,springboot也欢送。

@Import({Role.class, User.class})@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,        DataSourceTransactionManagerAutoConfiguration.class})public class Application {    public static void main(String[] args) {        new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);    }}

能够将@Import加到springboot的启动类上。

这样也能失效?

springboot的启动类个别都会加@SpringBootApplication注解,该注解上加了@SpringBootConfiguration注解。

而@SpringBootConfiguration注解,下面又加了@Configuration注解所以,springboot启动类自身带有@Configuration注解的性能。

意不意外?惊不惊喜?

4.2 Configuration类

下面介绍了@Import注解导入一般类的办法,它同时也反对导入Configuration类。

先定义一个Configuration类:

@Configurationpublic class MyConfig2 {    @Bean    public User user() {        return  new User();    }    @Bean    public Role role() {        return new Role();    }}

而后在另外一个Configuration类中引入后面的Configuration类:

@Import({MyConfig2.class})@Configurationpublic class MyConfig {}

这种形式,如果MyConfig2类曾经在spring指定的扫描目录或者子目录下,则MyConfig类会显得有点多余。因为MyConfig2类自身就是一个配置类,它外面就能定义bean。

但如果MyConfig2类不在指定的spring扫描目录或者子目录下,则通过MyConfig类的导入性能,也能把MyConfig2类辨认成配置类。这就有点厉害了喔。

其实上面还有更高端的玩法

swagger作为一个优良的文档生成框架,在spring我的项目中越来越受欢迎。接下来,咱们以swagger2为例,介绍一下它是如何导入相干类的。

家喻户晓,咱们引入swagger相干jar包之后,只须要在springboot的启动类上加上@EnableSwagger2注解,就能开启swagger的性能。

其中@EnableSwagger2注解中导入了Swagger2DocumentationConfiguration类。

该类是一个Configuration类,它又导入了另外两个类:

  • SpringfoxWebMvcConfiguration
  • SwaggerCommonConfiguration

SpringfoxWebMvcConfiguration类又会导入新的Configuration类,并且通过@ComponentScan注解扫描了一些其余的门路。

SwaggerCommonConfiguration同样也通过@ComponentScan注解扫描了一些额定的门路。

如此一来,咱们通过一个简略的@EnableSwagger2注解,就能轻松的导入swagger所需的一系列bean,并且领有swagger的性能。

还有什么好说的,狂终点赞,几乎完满。

4.3 ImportSelector

下面提到的Configuration类,它的性能十分弱小。但怎么说呢,它不太适宜加简单的判断条件,依据某些条件定义这些bean,依据另外的条件定义那些bean。

那么,这种需要该怎么实现呢?

这时就能够应用ImportSelector接口了。

首先定义一个类实现ImportSelector接口:

public class DataImportSelector implements ImportSelector {    @Override    public String[] selectImports(AnnotationMetadata importingClassMetadata) {        return new String[]{"com.sue.async.service.User", "com.sue.async.service.Role"};    }}

重写selectImports办法,在该办法中指定须要定义bean的类名,留神要蕴含残缺门路,而非相对路径。

而后在MyConfig类上@Import导入这个类即可:

@Import({DataImportSelector.class})@Configurationpublic class MyConfig {}

敌人们是不是又发现了一个新大陆?

不过,这个注解还有更牛逼的用处。

@EnableAutoConfiguration注解中导入了AutoConfigurationImportSelector类,并且外面蕴含零碎参数名称:spring.boot.enableautoconfigurationAutoConfigurationImportSelector类实现了ImportSelector接口。

并且重写了selectImports办法,该办法会依据某些注解去找所有须要创立bean的类名,而后返回这些类名。其中在查找这些类名之前,先调用isEnabled办法,判断是否须要持续查找。该办法会依据ENABLED\_OVERRIDE\_PROPERTY的值来作为判断条件。而这个值就是spring.boot.enableautoconfiguration

换句话说,这里能依据零碎参数管制bean是否须要被实例化,优良。

我集体认为实现ImportSelector接口的益处次要有以下两点:

  1. 把某个性能的相干类,能够放到一起,方面治理和保护。
  2. 重写selectImports办法时,可能依据条件判断某些类是否须要被实例化,或者某个条件实例化这些bean,其余的条件实例化那些bean等。咱们可能非常灵活的定制化bean的实例化。

4.4 ImportBeanDefinitionRegistrar

咱们通过下面的这种形式,的确可能非常灵活的自定义bean。

但它的自定义能力,还是无限的,它没法自定义bean的名称和作用域等属性。

有需要,就有解决方案。

接下来,咱们一起看看ImportBeanDefinitionRegistrar接口的神奇之处。

先定义CustomImportSelector类实现ImportBeanDefinitionRegistrar接口:

public class CustomImportSelector implements ImportBeanDefinitionRegistrar {    @Override    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {        RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class);        registry.registerBeanDefinition("role", roleBeanDefinition);        RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);        userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);        registry.registerBeanDefinition("user", userBeanDefinition);    }}

重写registerBeanDefinitions办法,在该办法中咱们能够获取BeanDefinitionRegistry对象,通过它去注册bean。不过在注册bean之前,咱们先要创立BeanDefinition对象,它外面能够自定义bean的名称、作用域等很多参数。

而后在MyConfig类上导入下面的类:

@Import({CustomImportSelector.class})@Configurationpublic class MyConfig {}

咱们所相熟的fegin性能,就是应用ImportBeanDefinitionRegistrar接口实现的:具体细节就不多说了,有趣味的敌人能够加我微信找我私聊。

5. PostProcessor

除此之外,spring还提供了专门注册bean的接口:BeanDefinitionRegistryPostProcessor

该接口的办法postProcessBeanDefinitionRegistry上有这样一段形容:批改应用程序上下文的外部bean定义注册表规范初始化。所有惯例bean定义都将被加载,然而还没有bean被实例化。这容许进一步增加在下一个后处理阶段开始之前定义bean。

如果用这个接口来定义bean,咱们要做的事件就变得非常简单了。只需定义一个类实现BeanDefinitionRegistryPostProcessor接口。

@Componentpublic class MyRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {    @Override    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {        RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class);        registry.registerBeanDefinition("role", roleBeanDefinition);        RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);        userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);        registry.registerBeanDefinition("user", userBeanDefinition);    }    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {    }}

重写postProcessBeanDefinitionRegistry办法,在该办法中可能获取BeanDefinitionRegistry对象,它负责bean的注册工作。

不过仔细的敌人可能会发现,外面还多了一个postProcessBeanFactory办法,没有做任何实现。

这个办法其实是它的父接口:BeanFactoryPostProcessor里的办法。

在应用程序上下文的规范bean工厂之后批改其外部bean工厂初始化。所有bean定义都已加载,但没有bean将被实例化。这容许重写或增加属性甚至能够初始化bean。

@Componentpublic class MyPostProcessor implements BeanFactoryPostProcessor {    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {        DefaultListableBeanFactory registry = (DefaultListableBeanFactory)beanFactory;        RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class);        registry.registerBeanDefinition("role", roleBeanDefinition);        RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);        userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);        registry.registerBeanDefinition("user", userBeanDefinition);    }}

既然这两个接口都能注册bean,那么他们有什么区别?

  • BeanDefinitionRegistryPostProcessor 更侧重于bean的注册
  • BeanFactoryPostProcessor 更侧重于对曾经注册的bean的属性进行批改,尽管也能够注册bean。

此时,有些敌人可能会问:既然拿到BeanDefinitionRegistry对象就能注册bean,那通过BeanFactoryAware的形式是不是也能注册bean呢?

从上面这张图可能看出DefaultListableBeanFactory就实现了BeanDefinitionRegistry接口。

这样一来,咱们如果可能获取DefaultListableBeanFactory对象的实例,而后调用它的注册办法,不就能够注册bean了?

说时迟那时快,定义一个类实现BeanFactoryAware接口:

@Componentpublic class BeanFactoryRegistry implements BeanFactoryAware {    @Override    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {        DefaultListableBeanFactory registry = (DefaultListableBeanFactory) beanFactory;        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class);        registry.registerBeanDefinition("user", rootBeanDefinition);        RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);        userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);        registry.registerBeanDefinition("user", userBeanDefinition);    }}

重写setBeanFactory办法,在该办法中可能获取BeanFactory对象,它可能强制转换成DefaultListableBeanFactory对象,而后通过该对象的实例注册bean。

当你满怀喜悦的运行我的项目时,发现居然报错了:

为什么会报错?

spring中bean的创立过程程序大抵如下:BeanFactoryAware接口是在bean创立胜利,并且实现依赖注入之后,在真正初始化之前才被调用的。在这个时候去注册bean意义不大,因为这个接口是给咱们获取bean的,并不倡议去注册bean,会引发很多问题。

最近无意间取得一份BAT大厂大佬写的刷题笔记,一下子买通了我的任督二脉,越来越感觉算法没有设想中那么难了。
BAT大佬写的刷题笔记,让我offer拿到手软

此外,ApplicationContextRegistry和ApplicationListener接口也有相似的问题,咱们能够用他们获取bean,但不倡议用它们注册bean。

最初说一句(求关注,别白嫖我)

如果这篇文章对您有所帮忙,或者有所启发的话,帮忙扫描下发二维码关注一下,您的反对是我保持写作最大的能源。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、工夫治理有超赞的粉丝福利,另外回复:加群,能够跟很多BAT大厂的前辈交换和学习。