Spring 加载Bean的方式(8种)
关于bean的加载方式,spring提供了各种各样的形式。因为spring管理bean整体上来说就是由spring维护对象的生命周期,所以bean的加载可以从大的方面划分成2种形式。已知类并交给spring管理,和已知类名并交给spring管理。有什么区别?一个给.class,一个给类名字符串。内部其实都一样,都是通过spring的BeanDefinition对象初始化spring的bean。如果前面这句话看起来有障碍,可以去复习一下spring的相关知识。
总的来说,梳理归纳,Spring有如下8中加载bean的方式
1. xml + <bean/>
2. xml:context + 注解(@Component+4个@Bean)
3. 配置类+扫描 + 注解(@Component+4个@Bean),这里也包括用@ImportResource 导入xml配置的bean
4. @Import导入bean的类 @Import导入配置类
5. AnnotationConfigApplicationContext调用register方法
6. @Import导入ImportSelector接口
7. @Import导入ImportBeanDefinitionRegistrar接口
8. @Import导入BeanDefinitionRegistryPostProcessor接口
下面详细展开详细的分享。
(1)普通的java类文件
package com.hao.bean; public class Dog { }
package com.hao.bean; public class Cat { public Cat(){ } int age; public Cat(int age){ this.age = age; } @Override public String toString() { return "Cat{" + "age=" + age + '}'; } }
bean>标签配置定义bean
applicationCOntext1.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--xml方式声明自己开发的bean--> <bean id="cat" class="com.hao.bean.Cat"/> <bean class="com.hao.bean.Dog"/> <!--xml方式声明第三方开发的bean--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/> <bean class="com.alibaba.druid.pool.DruidDataSource"/> <bean class="com.alibaba.druid.pool.DruidDataSource"/> </beans>
(3)测试Spring加载结果
1)加载代码
package com.hao.app; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App1 { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationCOntext1.xml"); // Object cat = ctx.getBean("cat"); // System.out.println(cat); // Dog dog = ctx.getBean(Dog.class); // System.out.println(dog); String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } } }
2)运行结果
package com.hao.bean; import org.springframework.stereotype.Component; @Component("tom") public class Cat { public Cat(){ } int age; public Cat(int age){ this.age = age; } @Override public String toString() { return "Cat{" + "age=" + age + '}'; } }
package com.hao.bean; import org.springframework.stereotype.Service; @Service("jerry") public class Mouse { }
当然,由于我们无法在第三方提供的技术源代码中去添加上述4个注解,因此当你需要加载第三方开发的bean的时候可以使用下列方式定义注解式的bean。
@Bean定义在一个方法上方,当前方法的返回值就可以交给spring管控,记得这个方法所在的类一定要定义在@Component修饰的类中,有人会说不是@Configuration吗?建议把spring注解开发相关课程学习一下,就不会有这个疑问了。
方式2:使用@Bean定义第三方bean,并将所在类定义为配置类或Bean
package com.hao.config; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import com.alibaba.druid.pool.DruidDataSource; @Component
// @Component和@Configuration 均可以,但是企业级应用中推荐使用@Configuration,可以明确为配置类 public class DbConfig { @Bean public DruidDataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); return ds; } }
上面提供的仅仅是bean的声明,spring并没有感知到这些东西,像极了上课积极回答问题的你,手举的非常高,可惜老师都没有往你的方向看上一眼。想让spring感知到这些积极的小伙伴,必须设置spring去检查这些类,看他们是否贴标签,想当积极分子。可以通过下列xml配置设置spring去检查哪些包,发现定了对应注解,就将对应的类纳入spring管控范围,声明成bean。
context:component-scan
applicationCOntext2.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!--指定加载bean的位置,component--> <context:component-scan base-package="com.hao.bean,com.hao.config"/> </beans>
方式二声明bean的方式是目前企业中较为常见的bean的声明方式,但是也有缺点。方式一中,通过一个配置文件,你可以查阅当前spring环境中定义了多少个或者说多少种bean,但是方式二没有任何一个地方可以查阅整体信息,只有当程序运行起来才能感知到加载了多少个bean。
(3)测试Spring加载结果
1)加载代码
package com.hao.app; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App2 { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationCOntext2.xml"); String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } } }
2)运行结果
package com.hao.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import com.alibaba.druid.pool.DruidDataSource;
// @Configuration 配置项如果不用于被扫描可以省略 @ComponentScan({"com.hao.bean","com.hao.config"}) public class SpringConfig3 { @Bean public DruidDataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); return ds; } }
注意: SpringConfig3上面的@Configuration 可写可不写。但是记得,如果用AnnotationConfigApplicationContext对象加载这个类,那@Configuration可以不写;但是如果不是加载,而是被别人加载,那一定要加上@Configuration,否则它就不是配置类,不会生效了。
但是一般企业级中,像 SpringConfig3 是一个总配置类,总配置类中只需要做一些最基础、最顶层的设计工作就可以了。 @Bean 定义的类应该被单独放到一个@Configuration配置类中。
(2)测试Spring加载结果
1)加载代码
package com.hao.app; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.hao.config.SpringConfig3; public class App3 { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class); String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } } }
2)运行结果
再补充一个小知识,由于早起开发的系统大部分都是采用xml的形式配置bean,现在的企业级开发基本上不用这种模式了。但是如果你特别幸运,需要基于之前的系统进行二次开发,这就尴尬了。新开发的用注解格式,之前开发的是xml格式。这个时候可不是让你选择用哪种模式的,而是两种要同时使用。spring提供了一个注解可以解决这个问题,@ImportResource,在配置类上直接写上要被融合的xml配置文件名即可,算的上一种兼容性解决方案,没啥实际意义。
package com.hao.config; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.stereotype.Component; @Configuration @ImportResource("applicationContext1.xml") public class SpringConfig32 { }
(2)测试Spring加载结果
1)加载代码
package com.hao.app; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.hao.config.SpringConfig3; public class App3 { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class); String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } } }
2)运行结果
使用扫描的方式加载bean是企业级开发中常见的bean的加载方式,但是由于扫描的时候不仅可以加载到你要的东西,还有可能加载到各种各样的乱七八糟的东西,万一没有控制好得不偿失了。
有人就会奇怪,会有什么问题呢?比如你扫描了com.hao.service包,后来因为业务需要,又扫描了com.hao.dao包,你发现com.hao包下面只有service和dao这两个包,这就简单了,直接扫描com.hao就行了。但是万万没想到,十天后你加入了一个外部依赖包,里面也有com.itheima包,这下就热闹了,该来的不该来的全来了。
所以我们需要一种精准制导的加载方式,使用@Import注解就可以解决你的问题。它可以加载所有的一切,只需要在注解的参数中写上加载的类对应的.class即可。有人就会觉得,还要自己手写,多麻烦,不如扫描好用。对呀,但是他可以指定加载啊,好的命名规范配合@ComponentScan可以解决很多问题,但是@Import注解拥有其重要的应用场景。有没有想过假如你要加载的bean没有使用@Component修饰呢?这下就无解了,而@Import就无需考虑这个问题。
被@Import进的bean的名字,是全路径类名。
package com.hao.bean; public class Dog { }
package com.hao.config; import org.springframework.context.annotation.Import; import com.hao.bean.Dog; @Import({Dog.class})
// 被导入的为普通的Class就行,无需使用注解声明为bean public class SpringConfig4 { }
此种方式的优势:1)此形式可以有效的降低源代码与Spring技术的耦合度,在spring技术底层及诸多框架的整合中大量使用。 2)使用别人的第三方代码时,除了@Bean, 还可以直接@Import,然后按类型get就行。
(2)测试Spring加载结果
1)加载代码
package com.itheima.app; import com.itheima.config.SpringConfig4; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class App4 { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class); String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } System.out.println("----------------------"); } }
2)运行结果
除了加载bean,还可以使用@Import注解加载配置类。其实本质上是一样的。 但是注意,这种@Import方式加载进spring的配置Bean的名字(全路径类名)和前面扫描加载进spring的配置bean的名字(类名小写)不同,具体可见方式二中和本方式的测试代码的运行效果。
package com.hao.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.alibaba.druid.pool.DruidDataSource; @Configuration // 实际山,当被使用@Import 方式导入时, 无论 @Configuration 注解是否有,都可以将 DbConfig和dataSource 加载到Spring容器中 public class DbConfig { @Bean public DruidDataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); return ds; } }
package com.hao.config; import org.springframework.context.annotation.Import; import com.hao.bean.Dog; @Import({Dog.class,DbConfig.class}) public class SpringConfig4 { }
(2)测试Spring加载结果
1)加载代码
package com.hao.app; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.hao.config.SpringConfig3; public class App3 { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class); String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } } }
2)运行结果
前面介绍的加载bean的方式都是在容器启动阶段完成bean的加载,下面这种方式就比较特殊了,可以在容器初始化完成后手动加载bean。通过这种方式可以实现编程式控制bean的加载。这种方式平时应用开发中不常用,但是在框架开发中会使用。
package com.hao.bean; public class Cat { public Cat(){ } int age; public Cat(int age){ this.age = age; } @Override public String toString() { return "Cat{" + "age=" + age + '}'; } }
package com.hao.bean; public class Mouse { }
package com.hao.app; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.hao.config.SpringConfig4; public class App5 { public static void main(String[] args) { // ApplicationContext对象做不了,只能用AnnotationConfigApplicationContext对象 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class); //上下文容器对象已经初始化完毕后,手工加载bean ctx.registerBean("tom", Cat.class,0); ...... ctx.register(Mouse.class);
...... } }
(2)测试Spring加载结果
1)加载代码
package com.hao.app; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.hao.bean.Cat; import com.hao.bean.Mouse; import com.hao.config.SpringConfig4; public class App5 { public static void main(String[] args) { // ApplicationContext对象做不了,只能用AnnotationConfigApplicationContext对象 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class); //上下文容器对象已经初始化完毕后,手工加载bean ctx.registerBean("tom", Cat.class,0); ctx.registerBean("tom", Cat.class,1); ctx.registerBean("tom", Cat.class,2); ctx.register(Mouse.class); String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } System.out.println("----------------------"); System.out.println(ctx.getBean(Cat.class)); } }
2)运行结果
现在又多了一种控制bean加载的方式,或者说是选择bean的方式。
在Spring源码中大量使用,通过导入实现了ImportSelector接口的类,实现对导入源的编程式处理
(1)注解配置类
package com.hao.bean; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata metadata) { // System.out.println("================"); // System.out.println("提示:"+metadata.getClassName()); // System.out.println(metadata.hasAnnotation("org.springframework.context.annotation.Configuration")); // Map<String, Object> attributes = metadata.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan"); // System.out.println(attributes); // System.out.println("================"); //各种条件的判定,判定完毕后,决定是否装在指定的bean boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration"); if(flag){ return new String[]{"com.hao.bean.Dog"}; } return new String[]{"com.hao.bean.Cat"}; } }
package com.hao.config; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import com.hao.bean.MyImportSelector; @Configuration //@ComponentScan(basePackages = "com.hao") @Import(MyImportSelector.class) public class SpringConfig6 { }
(2)测试Spring加载结果
1)加载代码
package com.hao.app; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.hao.config.SpringConfig6; public class App6 { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig6.class); String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } System.out.println("----------------------"); } }
2)运行结果
方式七:导入实现了ImportBeanDefinitionRegistrar接口的类
package com.hao.bean; import com.hao.bean.service.impl.BookServiceImpl2; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; public class MyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //1.使用元数据去做判定 BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition(); registry.registerBeanDefinition("bookService",beanDefinition); } }
package com.hao.config; import com.hao.bean.MyRegistrar; import org.springframework.context.annotation.Import; @Import(MyRegistrar.class) public class SpringConfig7 { }
(2)测试Spring加载结果
1)加载代码
package com.hao.app; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.hao.config.SpringConfig7; public class App7 { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig7.class); String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } System.out.println("----------------------"); } }
2)运行结果
方式八:导入实现了BeanDefinitionRegistryPostProcessor接口的类
上述七种方式都是在容器初始化过程中进行bean的加载或者声明,但是这里有一个bug。这么多种方式,它们之间如果有冲突怎么办?谁能有最终裁定权?这是个好问题,当某种类型的bean被接二连三的使用各种方式加载后,在你对所有加载方式的加载顺序没有完全理解清晰之前,你还真不知道最后谁说了算。即便你理清楚了,保不齐和你一起开发的猪队友又添加了一个bean,得嘞,这下就热闹了。
spring挥舞它仲裁者的大刀来了一个致命一击,都别哔哔了,我说了算,BeanDefinitionRegistryPostProcessor,看名字知道,BeanDefinition意思是bean定义,Registry注册的意思,Post后置,Processor处理器,全称bean定义后处理器,干啥的?在所有bean注册都折腾完后,它把最后一道关,说白了,它说了算,这下消停了,它是最后一个运行的。
总的来说,导入实现了BeanDefinitionRegistryPostProcessor接口的类,通过BeanDefinition的注册器注册实名bean, 可以实现对容器中bean的最终裁定
(1)
package com.hao.bean.service; public interface BookSerivce { void check(); }
package com.hao.bean.service.impl; import com.hao.bean.service.BookSerivce; import org.springframework.stereotype.Service; @Service("bookService") public class BookServiceImpl1 implements BookSerivce { @Override public void check() { System.out.println("book service 1.."); } }
package com.hao.bean.service.impl; import com.hao.bean.service.BookSerivce; public class BookServiceImpl2 implements BookSerivce { @Override public void check() { System.out.println("book service 2...."); } }
package com.hao.bean.service.impl; import com.hao.bean.service.BookSerivce; public class BookServiceImpl3 implements BookSerivce { @Override public void check() { System.out.println("book service 3......"); } }
......
package com.hao.bean; import com.hao.bean.service.impl.BookServiceImpl3; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; public class MyRegistrar2 implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //1.使用元数据去做判定 BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl3.class).getBeanDefinition(); registry.registerBeanDefinition("bookService",beanDefinition); } }
package com.hao.bean; import com.hao.bean.service.impl.BookServiceImpl4; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition(); registry.registerBeanDefinition("bookService",beanDefinition); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }
package com.hao.config; import org.springframework.context.annotation.Import; import com.hao.bean.MyPostProcessor; import com.hao.bean.MyRegistrar; import com.hao.bean.MyRegistrar2; import com.hao.bean.service.impl.BookServiceImpl1; @Import({BookServiceImpl1.class, MyPostProcessor.class, MyRegistrar2.class, MyRegistrar.class}) public class SpringConfig8 { }
(2)测试Spring加载结果
1)加载代码
package com.hao.app; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.hao.bean.service.BookSerivce; import com.hao.config.SpringConfig8; public class App8 { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig8.class); BookSerivce bookService = ctx.getBean("bookService", BookSerivce.class); bookService.check(); } }
2)运行结果
附注:以下讲解使用到的demo代码结构
总结
-
bean的定义由前期xml配置逐步演化成注解配置,本质是一样的,都是通过反射机制加载类名后创建对象,对象就是spring管控的bean
-
@Import注解可以指定加载某一个类作为spring管控的bean,如果被加载的类中还具有@Bean相关的定义,会被一同加载
-