惊呆了,Spring 中竟然有 12 种定义 bean 的方法
芋道源码 文章源自JAVA秀-https://www.javaxiu.com/37319.html
以下文章来源于苏三说技术,作者因为热爱所以坚持ing文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html 文章源自JAVA秀-https://www.javaxiu.com/37319.html 苏三说技术文章源自JAVA秀-https://www.javaxiu.com/37319.html 作者就职于知名互联网公司,掘金月度优秀作者,从事开发、架构和部分管理工作。实战经验丰富,对jdk、spring、springboot、springcloud、mybatis等开源框架源码有一定研究,欢迎关注,和我一起交流。文章源自JAVA秀-https://www.javaxiu.com/37319.html
点击上方“芋道源码”,选择“设为星标”文章源自JAVA秀-https://www.javaxiu.com/37319.html
管她前浪,还是后浪?文章源自JAVA秀-https://www.javaxiu.com/37319.html
能浪的浪,才是好浪!文章源自JAVA秀-https://www.javaxiu.com/37319.html
每天 8:55 更新文章,每天掉亿点点头发...文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html 源码精品专栏文章源自JAVA秀-https://www.javaxiu.com/37319.html
原创 | Java 2020 超神之路,很肝~文章源自JAVA秀-https://www.javaxiu.com/37319.html
中文详细注释的开源项目文章源自JAVA秀-https://www.javaxiu.com/37319.html
RPC 框架 Dubbo 源码解析文章源自JAVA秀-https://www.javaxiu.com/37319.html
网络应用框架 Netty 源码解析文章源自JAVA秀-https://www.javaxiu.com/37319.html
消息中间件 RocketMQ 源码解析文章源自JAVA秀-https://www.javaxiu.com/37319.html
数据库中间件 Sharding-JDBC 和 MyCAT 源码解析文章源自JAVA秀-https://www.javaxiu.com/37319.html
作业调度中间件 Elastic-Job 源码解析文章源自JAVA秀-https://www.javaxiu.com/37319.html
分布式事务中间件 TCC-Transaction 源码解析文章源自JAVA秀-https://www.javaxiu.com/37319.html
Eureka 和 Hystrix 源码解析文章源自JAVA秀-https://www.javaxiu.com/37319.html
Java 并发源码文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
前言文章源自JAVA秀-https://www.javaxiu.com/37319.html
1. xml文件配置bean文章源自JAVA秀-https://www.javaxiu.com/37319.html
1.1 构造器文章源自JAVA秀-https://www.javaxiu.com/37319.html
1.2 setter方法文章源自JAVA秀-https://www.javaxiu.com/37319.html
1.3 静态工厂文章源自JAVA秀-https://www.javaxiu.com/37319.html
1.4 实例工厂方法文章源自JAVA秀-https://www.javaxiu.com/37319.html
1.5 FactoryBean文章源自JAVA秀-https://www.javaxiu.com/37319.html
2. Component注解文章源自JAVA秀-https://www.javaxiu.com/37319.html
3. JavaConfig文章源自JAVA秀-https://www.javaxiu.com/37319.html
4. Import注解文章源自JAVA秀-https://www.javaxiu.com/37319.html
4.1 普通类文章源自JAVA秀-https://www.javaxiu.com/37319.html
4.2 Configuration类文章源自JAVA秀-https://www.javaxiu.com/37319.html
4.3 ImportSelector文章源自JAVA秀-https://www.javaxiu.com/37319.html
4.4 ImportBeanDefinitionRegistrar文章源自JAVA秀-https://www.javaxiu.com/37319.html
5. PostProcessor
文章源自JAVA秀-https://www.javaxiu.com/37319.html
前言
在庞大的java体系中,spring有着举足轻重的地位,它给每位开发者带来了极大的便利和惊喜。我们都知道spring是创建和管理bean的工厂,它提供了多种定义bean的方式,能够满足我们日常工作中的多种业务场景。文章源自JAVA秀-https://www.javaxiu.com/37319.html
那么问题来了,你知道spring中有哪些方式可以定义bean?文章源自JAVA秀-https://www.javaxiu.com/37319.html
我估计很多人会说出以下三种:文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
图片文章源自JAVA秀-https://www.javaxiu.com/37319.html
没错,但我想说的是以上三种方式只是开胃小菜,实际上spring的功能远比你想象中更强大。文章源自JAVA秀-https://www.javaxiu.com/37319.html
各位看官如果不信,请继续往下看。文章源自JAVA秀-https://www.javaxiu.com/37319.html
1. xml文件配置bean
我们先从xml配置bean
开始,它是spring最早支持的方式。后来,随着springboot
越来越受欢迎,该方法目前已经用得很少了,但我建议我们还是有必要了解一下。文章源自JAVA秀-https://www.javaxiu.com/37319.html
1.1 构造器
如果你之前有在bean.xml文件中配置过bean的经历,那么对如下的配置肯定不会陌生:文章源自JAVA秀-https://www.javaxiu.com/37319.html
<bean id="personService" class="com.sue.cache.service.test7.PersonService"></bean>
这种方式是以前使用最多的方式,它默认使用了无参构造器创建bean。文章源自JAVA秀-https://www.javaxiu.com/37319.html
当然我们还可以使用有参的构造器,通过<constructor-arg>
标签来完成配置。文章源自JAVA秀-https://www.javaxiu.com/37319.html
<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>
其中:文章源自JAVA秀-https://www.javaxiu.com/37319.html
index
表示下标,从0开始。文章源自JAVA秀-https://www.javaxiu.com/37319.htmlvalue
表示常量值文章源自JAVA秀-https://www.javaxiu.com/37319.htmlref
表示引用另一个bean文章源自JAVA秀-https://www.javaxiu.com/37319.html
1.2 setter方法
除此之外,spring还提供了另外一种思路:通过setter方法设置bean所需参数,这种方式耦合性相对较低,比有参构造器使用更为广泛。文章源自JAVA秀-https://www.javaxiu.com/37319.html
先定义Person实体:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Datapublic class Person { private String name; private int age;}
它里面包含:成员变量name和age,getter/setter方法。文章源自JAVA秀-https://www.javaxiu.com/37319.html
然后在bean.xml文件中配置bean时,加上<property>
标签设置bean所需参数。文章源自JAVA秀-https://www.javaxiu.com/37319.html
<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的静态方法。例如:文章源自JAVA秀-https://www.javaxiu.com/37319.html
public class SusanBeanFactory { public static Person createPerson(String name, int age) { return new Person(name, age); }}
接下来定义Person类如下:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@AllArgsConstructor@NoArgsConstructor@Datapublic class Person { private String name; private int age;}
它里面包含:成员变量name和age,getter/setter方法,无参构造器和全参构造器。文章源自JAVA秀-https://www.javaxiu.com/37319.html
然后在bean.xml文件中配置bean时,通过factory-method
参数指定静态工厂方法,同时通过<constructor-arg>
设置相关参数。文章源自JAVA秀-https://www.javaxiu.com/37319.html
<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的方法。文章源自JAVA秀-https://www.javaxiu.com/37319.html
public class SusanBeanFactory { public Person createPerson(String name, int age) { return new Person(name, age); }}
Person类跟上面一样,就不多说了。文章源自JAVA秀-https://www.javaxiu.com/37319.html
然后bean.xml文件中配置bean时,需要先配置工厂bean。然后在配置实例bean时,通过factory-bean
参数指定该工厂bean的引用。文章源自JAVA秀-https://www.javaxiu.com/37319.html
<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.5 FactoryBean
不知道大家有没有发现,上面的实例工厂方法每次都需要创建一个工厂类,不方面统一管理。文章源自JAVA秀-https://www.javaxiu.com/37319.html
这时我们可以使用FactoryBean
接口。文章源自JAVA秀-https://www.javaxiu.com/37319.html
public class SusanBeanFactory { public static Person createPerson(String name, int age) { return new Person(name, age); }}
在它的getObject
方法中可以实现我们自己的逻辑创建对象,并且在getObjectType
方法中我们可以定义对象的类型。文章源自JAVA秀-https://www.javaxiu.com/37319.html
然后在bean.xml文件中配置bean时,只需像普通的bean一样配置即可。文章源自JAVA秀-https://www.javaxiu.com/37319.html
<bean id="userFactoryBean" class="com.sue.async.service.UserFactoryBean"></bean>
轻松搞定,so easy。文章源自JAVA秀-https://www.javaxiu.com/37319.html
注意:getBean("userFactoryBean");获取的是getObject方法中返回的对象。而getBean("&userFactoryBean");获取的才是真正的UserFactoryBean对象。文章源自JAVA秀-https://www.javaxiu.com/37319.html
我们通过上面五种方式,在bean.xml文件中把bean配置好之后,spring就会自动扫描和解析相应的标签,并且帮我们创建和实例化bean,然后放入spring容器中。文章源自JAVA秀-https://www.javaxiu.com/37319.html
虽说基于xml文件的方式配置bean,简单而且非常灵活,比较适合一些小项目。但如果遇到比较复杂的项目,则需要配置大量的bean,而且bean之间的关系错综复杂,这样久而久之会导致xml文件迅速膨胀,非常不利于bean的管理。文章源自JAVA秀-https://www.javaxiu.com/37319.html
2. Component注解
为了解决bean太多时,xml文件过大,从而导致膨胀不好维护的问题。在spring2.5中开始支持:@Component
、@Repository
、@Service
、@Controller
等注解定义bean。文章源自JAVA秀-https://www.javaxiu.com/37319.html
如果你有看过这些注解的源码的话,就会惊奇得发现:其实后三种注解也是@Component
。文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
图片文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
图片文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
图片文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Component
系列注解的出现,给我们带来了极大的便利。我们不需要像以前那样在bean.xml文件中配置bean了,现在只用在类上加Component、Repository、Service、Controller,这四种注解中的任意一种,就能轻松完成bean的定义。文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Servicepublic class PersonService { public String get() { return "data"; }}
其实,这四种注解在功能上没有特别的区别,不过在业界有个不成文的约定:文章源自JAVA秀-https://www.javaxiu.com/37319.html
Controller 一般用在控制层文章源自JAVA秀-https://www.javaxiu.com/37319.html
Service 一般用在业务层文章源自JAVA秀-https://www.javaxiu.com/37319.html
Repository 一般用在数据层文章源自JAVA秀-https://www.javaxiu.com/37319.html
Component 一般用在公共组件上文章源自JAVA秀-https://www.javaxiu.com/37319.html
太棒了,简直一下子解放了我们的双手。文章源自JAVA秀-https://www.javaxiu.com/37319.html
不过,需要特别注意的是,通过这种@Component
扫描注解的方式定义bean的前提是:需要先配置扫描路径 。文章源自JAVA秀-https://www.javaxiu.com/37319.html
目前常用的配置扫描路径的方式如下:文章源自JAVA秀-https://www.javaxiu.com/37319.html
在applicationContext.xml文件中使用
<context:component-scan>
标签。例如:文章源自JAVA秀-https://www.javaxiu.com/37319.html
<context:component-scan base-package="com.sue.cache" />
在springboot的启动类上加上
@ComponentScan
注解,例如:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@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功能:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@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默认会从入口类的同一级或者子级的包去找。文章源自JAVA秀-https://www.javaxiu.com/37319.html
@SpringBootApplication(scanBasePackages = "com.sue.cache")public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}
此外,除了上述四种@Component
注解之外,springboot还增加了@RestController
注解,它是一种特殊的@Controller
注解,所以也是@Component
注解。文章源自JAVA秀-https://www.javaxiu.com/37319.html
@RestController
还支持@ResponseBody
注解的功能,即将接口响应数据的格式自动转换成json。文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
图片文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Component
系列注解已经让我们爱不释手了,它目前是我们日常工作中最多的定义bean的方式。文章源自JAVA秀-https://www.javaxiu.com/37319.html
3. JavaConfig
@Component
系列注解虽说使用起来非常方便,但是bean的创建过程完全交给spring容器来完成,我们没办法自己控制。文章源自JAVA秀-https://www.javaxiu.com/37319.html
spring从3.0以后,开始支持JavaConfig的方式定义bean。它可以看做spring的配置文件,但并非真正的配置文件,我们需要通过编码java代码的方式创建bean。例如:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Configurationpublic class MyConfiguration { @Bean public Person person() { return new Person(); }}
在JavaConfig类上加@Configuration
注解,相当于配置了<beans>
标签。而在方法上加@Bean
注解,相当于配置了<bean>
标签。文章源自JAVA秀-https://www.javaxiu.com/37319.html
此外,springboot还引入了一些列的@Conditional
注解,用来控制bean的创建。文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Configurationpublic class MyConfiguration { @ConditionalOnClass(Country.class) @Bean public Person person() { return new Person(); }}
@ConditionalOnClass
注解的功能是当项目中存在Country类时,才实例化Person类。换句话说就是,如果项目中不存在Country类,就不实例化Person类。文章源自JAVA秀-https://www.javaxiu.com/37319.html
这个功能非常有用,相当于一个开关控制着Person类,只有满足一定条件才能实例化。文章源自JAVA秀-https://www.javaxiu.com/37319.html
spring中使用比较多的Conditional还有:文章源自JAVA秀-https://www.javaxiu.com/37319.html
ConditionalOnBean文章源自JAVA秀-https://www.javaxiu.com/37319.html
ConditionalOnProperty文章源自JAVA秀-https://www.javaxiu.com/37319.html
ConditionalOnMissingClass文章源自JAVA秀-https://www.javaxiu.com/37319.html
ConditionalOnMissingBean文章源自JAVA秀-https://www.javaxiu.com/37319.html
ConditionalOnWebApplication文章源自JAVA秀-https://www.javaxiu.com/37319.html
如果你对这些功能比较感兴趣,可以看看《spring中那些让你爱不释手的代码技巧(续集)》,这是我之前写的一篇文章,里面做了更详细的介绍。文章源自JAVA秀-https://www.javaxiu.com/37319.html
下面用一张图整体认识一下@Conditional家族:文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
图片文章源自JAVA秀-https://www.javaxiu.com/37319.html
nice,有了这些功能,我们终于可以告别麻烦的xml时代了。文章源自JAVA秀-https://www.javaxiu.com/37319.html
4. Import注解
通过前面介绍的@Configuration和@Bean相结合的方式,我们可以通过代码定义bean。但这种方式有一定的局限性,它只能创建该类中定义的bean实例,不能创建其他类的bean实例,如果我们想创建其他类的bean实例该怎么办呢?文章源自JAVA秀-https://www.javaxiu.com/37319.html
这时可以使用@Import
注解导入。文章源自JAVA秀-https://www.javaxiu.com/37319.html
4.1 普通类
spring4.2之后@Import
注解可以实例化普通类的bean实例。例如:文章源自JAVA秀-https://www.javaxiu.com/37319.html
先定义了Role类:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Datapublic class Role { private Long id; private String name;}
接下来使用@Import注解导入Role类:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Import(Role.class)@Configurationpublic class MyConfig {}
然后在调用的地方通过@Autowired
注解注入所需的bean。文章源自JAVA秀-https://www.javaxiu.com/37319.html
@RequestMapping("/")@RestControllerpublic class TestController { @Autowired private Role role; @GetMapping("/test") public String test() { System.out.println(role); return "test"; }}
聪明的你可能会发现,我没有在任何地方定义过Role的bean,但spring却能自动创建该类的bean实例,这是为什么呢?文章源自JAVA秀-https://www.javaxiu.com/37319.html
这也许正是@Import
注解的强大之处。文章源自JAVA秀-https://www.javaxiu.com/37319.html
此时,有些朋友可能会问:@Import
注解能定义单个类的bean,但如果有多个类需要定义bean该怎么办呢?文章源自JAVA秀-https://www.javaxiu.com/37319.html
恭喜你,这是个好问题,因为@Import
注解也支持。文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Import({Role.class, User.class})@Configurationpublic class MyConfig {}
甚至,如果你想偷懒,不想写这种MyConfig
类,springboot也欢迎。文章源自JAVA秀-https://www.javaxiu.com/37319.html
@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的启动类上。文章源自JAVA秀-https://www.javaxiu.com/37319.html
这样也能生效?文章源自JAVA秀-https://www.javaxiu.com/37319.html
springboot的启动类一般都会加@SpringBootApplication注解,该注解上加了@SpringBootConfiguration注解。文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
图片文章源自JAVA秀-https://www.javaxiu.com/37319.html
而@SpringBootConfiguration注解,上面又加了@Configuration注解所以,springboot启动类本身带有@Configuration注解的功能。文章源自JAVA秀-https://www.javaxiu.com/37319.html
意不意外?惊不惊喜?文章源自JAVA秀-https://www.javaxiu.com/37319.html
4.2 Configuration类
上面介绍了@Import注解导入普通类的方法,它同时也支持导入Configuration类。文章源自JAVA秀-https://www.javaxiu.com/37319.html
先定义一个Configuration类:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Configurationpublic class MyConfig2 { @Bean public User user() { return new User(); } @Bean public Role role() { return new Role(); }}
然后在另外一个Configuration类中引入前面的Configuration类:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Import({MyConfig2.class})@Configurationpublic class MyConfig {}
这种方式,如果MyConfig2类已经在spring指定的扫描目录或者子目录下,则MyConfig类会显得有点多余。因为MyConfig2类本身就是一个配置类,它里面就能定义bean。文章源自JAVA秀-https://www.javaxiu.com/37319.html
但如果MyConfig2类不在指定的spring扫描目录或者子目录下,则通过MyConfig类的导入功能,也能把MyConfig2类识别成配置类。这就有点厉害了喔。文章源自JAVA秀-https://www.javaxiu.com/37319.html
其实下面还有更高端的玩法 。文章源自JAVA秀-https://www.javaxiu.com/37319.html
swagger作为一个优秀的文档生成框架,在spring项目中越来越受欢迎。接下来,我们以swagger2为例,介绍一下它是如何导入相关类的。文章源自JAVA秀-https://www.javaxiu.com/37319.html
众所周知,我们引入swagger相关jar包之后,只需要在springboot的启动类上加上@EnableSwagger2
注解,就能开启swagger的功能。文章源自JAVA秀-https://www.javaxiu.com/37319.html
其中@EnableSwagger2注解中导入了Swagger2DocumentationConfiguration类。文章源自JAVA秀-https://www.javaxiu.com/37319.html
该类是一个Configuration类,它又导入了另外两个类:文章源自JAVA秀-https://www.javaxiu.com/37319.html
SpringfoxWebMvcConfiguration文章源自JAVA秀-https://www.javaxiu.com/37319.html
SwaggerCommonConfiguration文章源自JAVA秀-https://www.javaxiu.com/37319.html
SpringfoxWebMvcConfiguration类又会导入新的Configuration类,并且通过@ComponentScan注解扫描了一些其他的路径。
文章源自JAVA秀-https://www.javaxiu.com/37319.html
SwaggerCommonConfiguration同样也通过@ComponentScan注解扫描了一些额外的路径。文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
图片文章源自JAVA秀-https://www.javaxiu.com/37319.html
如此一来,我们通过一个简单的@EnableSwagger2
注解,就能轻松的导入swagger所需的一系列bean,并且拥有swagger的功能。文章源自JAVA秀-https://www.javaxiu.com/37319.html
还有什么好说的,狂起点赞,简直完美。文章源自JAVA秀-https://www.javaxiu.com/37319.html
4.3 ImportSelector
上面提到的Configuration类,它的功能非常强大。但怎么说呢,它不太适合加复杂的判断条件,根据某些条件定义这些bean,根据另外的条件定义那些bean。文章源自JAVA秀-https://www.javaxiu.com/37319.html
那么,这种需求该怎么实现呢?文章源自JAVA秀-https://www.javaxiu.com/37319.html
这时就可以使用ImportSelector
接口了。文章源自JAVA秀-https://www.javaxiu.com/37319.html
首先定义一个类实现ImportSelector
接口:文章源自JAVA秀-https://www.javaxiu.com/37319.html
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的类名,注意要包含完整路径,而非相对路径。文章源自JAVA秀-https://www.javaxiu.com/37319.html
然后在MyConfig类上@Import导入这个类即可:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Import({DataImportSelector.class})@Configurationpublic class MyConfig {}
朋友们是不是又发现了一个新大陆?文章源自JAVA秀-https://www.javaxiu.com/37319.html
不过,这个注解还有更牛逼的用途。文章源自JAVA秀-https://www.javaxiu.com/37319.html
@EnableAutoConfiguration注解中导入了AutoConfigurationImportSelector类,并且里面包含系统参数名称:spring.boot.enableautoconfiguration
。AutoConfigurationImportSelector类实现了
ImportSelector
接口。文章源自JAVA秀-https://www.javaxiu.com/37319.html
并且重写了
selectImports
方法,该方法会根据某些注解去找所有需要创建bean的类名,然后返回这些类名。其中在查找这些类名之前,先调用isEnabled方法,判断是否需要继续查找。文章源自JAVA秀-https://www.javaxiu.com/37319.html
该方法会根据ENABLED_OVERRIDE_PROPERTY的值来作为判断条件。文章源自JAVA秀-https://www.javaxiu.com/37319.html
而这个值就是
spring.boot.enableautoconfiguration
。文章源自JAVA秀-https://www.javaxiu.com/37319.html
换句话说,这里能根据系统参数控制bean是否需要被实例化,优秀。文章源自JAVA秀-https://www.javaxiu.com/37319.html
我个人认为实现ImportSelector接口的好处主要有以下两点:文章源自JAVA秀-https://www.javaxiu.com/37319.html
把某个功能的相关类,可以放到一起,方面管理和维护。文章源自JAVA秀-https://www.javaxiu.com/37319.html
重写selectImports方法时,能够根据条件判断某些类是否需要被实例化,或者某个条件实例化这些bean,其他的条件实例化那些bean等。我们能够非常灵活的定制化bean的实例化。文章源自JAVA秀-https://www.javaxiu.com/37319.html
4.4 ImportBeanDefinitionRegistrar
我们通过上面的这种方式,确实能够非常灵活的自定义bean。文章源自JAVA秀-https://www.javaxiu.com/37319.html
但它的自定义能力,还是有限的,它没法自定义bean的名称和作用域等属性。文章源自JAVA秀-https://www.javaxiu.com/37319.html
有需求,就有解决方案。文章源自JAVA秀-https://www.javaxiu.com/37319.html
接下来,我们一起看看ImportBeanDefinitionRegistrar
接口的神奇之处。文章源自JAVA秀-https://www.javaxiu.com/37319.html
先定义CustomImportSelector类实现ImportBeanDefinitionRegistrar接口:文章源自JAVA秀-https://www.javaxiu.com/37319.html
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的名称、作用域等很多参数。文章源自JAVA秀-https://www.javaxiu.com/37319.html
然后在MyConfig类上导入上面的类:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@Import({CustomImportSelector.class})@Configurationpublic class MyConfig {}
我们所熟悉的fegin功能,就是使用ImportBeanDefinitionRegistrar接口实现的:具体细节就不多说了,有兴趣的朋友可以加我微信找我私聊。文章源自JAVA秀-https://www.javaxiu.com/37319.html
5. PostProcessor
除此之外,spring还提供了专门注册bean的接口:BeanDefinitionRegistryPostProcessor
。文章源自JAVA秀-https://www.javaxiu.com/37319.html
该接口的方法postProcessBeanDefinitionRegistry上有这样一段描述:修改应用程序上下文的内部bean定义注册表标准初始化。所有常规bean定义都将被加载,但是还没有bean被实例化。这允许进一步添加在下一个后处理阶段开始之前定义bean。文章源自JAVA秀-https://www.javaxiu.com/37319.html
如果用这个接口来定义bean,我们要做的事情就变得非常简单了。只需定义一个类实现BeanDefinitionRegistryPostProcessor
接口。文章源自JAVA秀-https://www.javaxiu.com/37319.html
@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的注册工作。文章源自JAVA秀-https://www.javaxiu.com/37319.html
不过细心的朋友可能会发现,里面还多了一个postProcessBeanFactory
方法,没有做任何实现。文章源自JAVA秀-https://www.javaxiu.com/37319.html
这个方法其实是它的父接口:BeanFactoryPostProcessor
里的方法。文章源自JAVA秀-https://www.javaxiu.com/37319.html
在应用程序上下文的标准bean工厂之后修改其内部bean工厂初始化。所有bean定义都已加载,但没有bean将被实例化。这允许重写或添加属性甚至可以初始化bean。文章源自JAVA秀-https://www.javaxiu.com/37319.html
@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,那么他们有什么区别?文章源自JAVA秀-https://www.javaxiu.com/37319.html
BeanDefinitionRegistryPostProcessor 更侧重于bean的注册文章源自JAVA秀-https://www.javaxiu.com/37319.html
BeanFactoryPostProcessor 更侧重于对已经注册的bean的属性进行修改,虽然也可以注册bean。文章源自JAVA秀-https://www.javaxiu.com/37319.html
此时,有些朋友可能会问:既然拿到BeanDefinitionRegistry对象就能注册bean,那通过BeanFactoryAware的方式是不是也能注册bean呢?文章源自JAVA秀-https://www.javaxiu.com/37319.html
从下面这张图能够看出DefaultListableBeanFactory就实现了BeanDefinitionRegistry接口。文章源自JAVA秀-https://www.javaxiu.com/37319.html
这样一来,我们如果能够获取DefaultListableBeanFactory对象的实例,然后调用它的注册方法,不就可以注册bean了?文章源自JAVA秀-https://www.javaxiu.com/37319.html
说时迟那时快,定义一个类实现BeanFactoryAware
接口:文章源自JAVA秀-https://www.javaxiu.com/37319.html
@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。文章源自JAVA秀-https://www.javaxiu.com/37319.html
当你满怀喜悦的运行项目时,发现竟然报错了:文章源自JAVA秀-https://www.javaxiu.com/37319.html
为什么会报错?文章源自JAVA秀-https://www.javaxiu.com/37319.html
spring中bean的创建过程顺序大致如下:BeanFactoryAware
接口是在bean创建成功,并且完成依赖注入之后,在真正初始化之前才被调用的。在这个时候去注册bean意义不大,因为这个接口是给我们获取bean的,并不建议去注册bean,会引发很多问题。文章源自JAVA秀-https://www.javaxiu.com/37319.html
此外,ApplicationContextRegistry和ApplicationListener接口也有类似的问题,我们可以用他们获取bean,但不建议用它们注册bean。文章源自JAVA秀-https://www.javaxiu.com/37319.html
- END -文章源自JAVA秀-https://www.javaxiu.com/37319.html
欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
已在知识星球更新源码解析如下:文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。文章源自JAVA秀-https://www.javaxiu.com/37319.html
提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。文章源自JAVA秀-https://www.javaxiu.com/37319.html
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章源自JAVA秀-https://www.javaxiu.com/37319.html
文章有帮助的话,在看,转发吧。谢谢支持哟 (*^__^*)文章源自JAVA秀-https://www.javaxiu.com/37319.html
阅读原文文章源自JAVA秀-https://www.javaxiu.com/37319.html

评论