前言
在宏大的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的前提是:须要先配置扫描门路。
目前罕用的配置扫描门路的形式如下:
- 在applicationContext.xml文件中应用
<context:component-scan>
标签。例如:
<context:component-scan base-package="com.sue.cache" />
- 在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); }}
- 间接在
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.enableautoconfiguration
。AutoConfigurationImportSelector类实现了ImportSelector
接口。
并且重写了selectImports
办法,该办法会依据某些注解去找所有须要创立bean的类名,而后返回这些类名。其中在查找这些类名之前,先调用isEnabled办法,判断是否须要持续查找。该办法会依据ENABLED\_OVERRIDE\_PROPERTY的值来作为判断条件。而这个值就是spring.boot.enableautoconfiguration
。
换句话说,这里能依据零碎参数管制bean是否须要被实例化,优良。
我集体认为实现ImportSelector接口的益处次要有以下两点:
- 把某个性能的相干类,能够放到一起,方面治理和保护。
- 重写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大厂的前辈交换和学习。