目录
1.将xml配置文件改为配置类
2.@Configuration的proxyBeanMethod属性
3.bean管理方式
4.使用@Import注解注入bean
?注:注解格式导入注解配置bean
5. 编程形式注册bean
?6.导入实现了ImportSelector接口的类??????
?7.导入实现了ImportBeanDefinitionRegistrar接口的类
?8.导入实现了BeanDefinitionRegistryPostProcessor接口的类
总结
bean的加载控制
1.bean的加载控制(编程式)
2. bean的加载控制(注解式)
3. 指定类上根据条件选择性加载
1.将xml配置文件改为配置类
????????如果以前使用xml配置bean,现在想改用注解方式的配置类,如果将xml内的改写太麻烦,而且可能出错,想加载配置类的同时也加载xml配置文件,使用@ImpotResource注解实现。
@ImportResouce("applicationContext.xml") //将applicationContext.xml注入进来,使用一个容器
public class MyConfig {
}
2.@Configuration的proxyBeanMethod属性
????????spring中有两种生成代理对象的方式:jdk动态代理和cglib动态代理
??????? 在配置类上注解@Configuration中有proxyBeanMethod属性,默认值为true,即:
????????@Configuration=@Configuration(proxyBeanMethod=true)
??????? 这样配置类里@Bean生成的对象是使用cglib代理生成的代理对象,这样在其他地方不管调用几次代理对象都是同一个。
??????? 将proxyBeanMethod属性改为false,就不是代理对象了,每次调用生成一个新的对象
????????@Configuration(proxyBeanMethod=true)
3.bean管理方式
?????? 前三种在这。承接上文
参考Spring 加载Bean的方式(8种) - 郝志锋 - 博客园
4.使用@Import注解注入bean
????????使用扫描的方式加载bean是企业级开发中常见的bean的加载方式,但是由于扫描的时候不仅可以加载到你要的东西,还有可能加载到各种各样的乱七八糟的东西,万一没有控制好得不偿失了。
????????所以我们需要一种精准制导的加载方式,使用@Import注解就可以解决你的问题。它可以加载所有的一切,只需要在注解的参数中写上加载的类对应的.class即可。有人就会觉得,还要自己手写,多麻烦,不如扫描好用。对呀,但是他可以指定加载啊,好的命名规范配合@ComponentScan可以解决很多问题,但是@Import注解拥有其重要的应用场景。有没有想过假如你要加载的bean没有使用@Component修饰呢?这下就无解了,而@Import就无需考虑这个问题。
????????被@Import进的bean的名字,是全路径类名。
public class Dog {
}
@Import({Dog.class})
// 被导入的为普通的Class就行,无需使用注解声明为bean
public class SpringConfig4 {
}
测试:
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("----------------------");
}
}
?
??????? 这样在User类和Book类不需要使用@Component等注解就配置了对应bean,而且创建迅速,当导入的时候就容器中有了对应bean,可直接使用了,也体现了spring的无侵入式编程,降低与spring技术的耦合度。
??????? 也可以导入一个配置类,且配置其中的所有bean。
?注:注解格式导入注解配置bean
????????除了加载bean,还可以使用@Import注解加载配置类。其实本质上是一样的。? 但是注意,这种@Import方式加载进spring的配置Bean的名字(全路径类名)和前面扫描加载进spring的配置bean的名字(类名小写)不同,具体可见方式二中和本方式的测试代码的运行效果。
@Configuration
// 实际山,当被使用@Import 方式导入时, 无论 @Configuration 注解是否有,都可以将 DbConfig和dataSource 加载到Spring容器中
public class DbConfig {
@Bean
public DruidDataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
@Import({Dog.class,DbConfig.class})
public class SpringConfig4 {
}
测试:
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);
}
}
}
?
?
5. 编程形式注册bean
????????前面介绍的加载bean的方式都是在容器启动阶段完成bean的加载,下面这种方式就比较特殊了,可以在容器初始化完成后手动加载bean。通过这种方式可以实现编程式控制bean的加载。这种方式平时应用开发中不常用,但是在框架开发中会使用。
public class Cat {
public Cat(){
}
int age;
public Cat(int age){
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"age=" + age +
'}';
}
}
public class Mouse {
}
测试:?
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));
}
}
?6.导入实现了ImportSelector接口的类??????
??????? bean的加载可以进行编程化的控制,添加if语句就可以实现bean的加载控制了。但是毕竟是在容器初始化后实现bean的加载控制,那是否可以在容器初始化过程中进行控制呢?答案是必须的。实现ImportSelector接口的类可以设置加载的bean的全路径类名,记得一点,只要能编程就能判定,能判定意味着可以控制程序的运行走向,进而控制一切。
????????现在又多了一种控制bean加载的方式,或者说是选择bean的方式。
????????在Spring源码中大量使用,通过导入实现了ImportSelector接口的类,实现对导入源的编程式处理
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"};
}
}
@Configuration
//@ComponentScan(basePackages = "com.hao")
@Import(MyImportSelector.class)
public class 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("----------------------");
}
}
?7.导入实现了ImportBeanDefinitionRegistrar接口的类
??????? spring中定义了一个叫做BeanDefinition的东西,它才是控制bean初始化加载的核心。BeanDefinition接口中给出了若干种方法,可以控制bean的相关属性。说个最简单的,创建的对象是单例还是非单例,在BeanDefinition中定义了scope属性就可以控制这个。如果你感觉方式六没有给你开放出足够的对bean的控制操作,那么方式七你值得拥有。我们可以通过定义一个类,然后实现ImportBeanDefinitionRegistrar接口的方式定义bean,并且还可以让你对bean的初始化进行更加细粒度的控制。
????????导入实现了ImportBeanDefinitionRegistrar接口的类,通过BeanDefinition的注册器注册实名bean,实现对 容器中bean的裁定,例如对现有bean的覆盖,进而达成不修改源代码的情况下更换实现的效果
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);
}
}
@Import(MyRegistrar.class)
public class 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("----------------------");
}
}
?8.导入实现了BeanDefinitionRegistryPostProcessor接口的类
????????上述七种方式都是在容器初始化过程中进行bean的加载或者声明,但是这里有一个bug。这么多种方式,它们之间如果有冲突怎么办?谁能有最终裁定权?这是个好问题,当某种类型的bean被接二连三的使用各种方式加载后,在你对所有加载方式的加载顺序没有完全理解清晰之前,你还真不知道最后谁说了算。?
????????导入实现了BeanDefinitionRegistryPostProcessor接口的类,通过BeanDefinition的注册器注册实名bean, 可以实现对容器中bean的最终裁定
public interface BookSerivce {
void check();
}
@Service("bookService")
public class BookServiceImpl1 implements BookSerivce {
@Override
public void check() {
System.out.println("book service 1..");
}
}
public class BookServiceImpl2 implements BookSerivce {
@Override
public void check() {
System.out.println("book service 2....");
}
}
public class BookServiceImpl3 implements BookSerivce {
@Override
public void check() {
System.out.println("book service 3......");
}
}
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);
}
}
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 {
}
}
@Import({BookServiceImpl1.class, MyPostProcessor.class, MyRegistrar2.class, MyRegistrar.class})
public class 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();
}
}
?
总结
-
bean的定义由前期xml配置逐步演化成注解配置,本质是一样的,都是通过反射机制加载类名后创建对象,对象就是spring管控的bean -
@Import注解可以指定加载某一个类作为spring管控的bean,如果被加载的类中还具有@Bean相关的定义,会被一同加载 -
spring开放出了若干种可编程控制的bean的初始化方式,通过分支语句由固定的加载bean转成了可以选择bean是否加载或者选择加载哪一种bean
bean的加载控制
- AnnotationConfigApplicationContext调用register方法
- @Import导入ImportSelector接口
- @Import导入ImportBeanDefinitionRegistrar接口
- @Import导入BeanDefinitionRegistryPostProcessor接口
1.bean的加载控制(编程式)
以ImportSelector为例
1.根据任意条件确认是否加载bean
2. bean的加载控制(注解式)
需要导入springboot
?使用@Conditional注解的派生注解设置各种组合条件控制bean的加载
格式:@ConditionalOn***
//@Import(MyImportSelector.class)
@Import(Mouse.class)
public class SpringConfig {
@Bean
//@ConditionalOnClass(Mouse.class) 一般不使用这种方式
//@ConditionalOnClass(name = "com.huangzx.bean.Mouse") //有Mouse时加载
//@ConditionalOnMissingClass("com.huangzx.bean.Wolf") //没有Wolf时加载
@ConditionalOnBean(name = "jerry") //有Mouse bean,且Mouse的名字是jerry时加载
@ConditionalOnMissingClass("com.huangzx.bean.Dog") //有Mouse bean,且Mouse的名字是jerry时加载,且没有Dog时加载
@ConditionalOnWebApplication //是web程序时加载
public Cat tom(){
return new Cat();
}
}
3. 指定类上根据条件选择性加载
?
后续可以单独配置bean,当有某种环境时,加载某些bean
这里当有mysql数据库连接时,加载Druid数据源
public class SpringConfig {
@Bean
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public DruidDataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
?
|