SpringBoot--Bean管理详解
Bean管理
Bean扫描
回顾spring:
在XML配置文件中,可以借助
<context:component-scan base-package = "com.lyc">
或者注解
@ComponentScan(basePackages="com.lyc")
再springboot项目中,既没有标签,也没有写注解,但他仍然可以扫描到我们写的业务代码。这是为什么呢?
原因如下:
在springboot启动类中的@springBootApplication是一个组合注解。
@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication {
其中就组合了@ComponentScan注解,因此可以扫描Bean对象。
注意事项:这相当于在启动类上添加了@ComponentScan注解,所以默认只能扫描启动类所在的包及其子包,而其他包扫描不到。如果想扫描其他包,需要手动添加@ComponentScan注解。
Bean注册
回顾spring:
在spring项目中,我们可以添加以下注解来进行Bean的注册
注解 | 说明 | 位置 |
---|---|---|
@Component | 声明bean的基础注解 | 不属于以下三类时,用此注解 |
@Service | @Component的衍生注解 | 标注在业务类上 |
@Controller | @Component的衍生注解 | 标注在控制类上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上(由于与MyBatis整合,用得少) |
问题:在spring项目中,如果将三方jar包中的Bean对象注入到IOC容器中,这些注解还能使用吗?
解答:不能,在spring项目中这些jar包被设为只读,不能修改,因此:
如果要注册的Bean对象来自第三方(不是自定义),是无法使用@Component及衍生注解的声明Bean的
如何解决?
spring为我们提供了两个注解解决问题:
-
@Bean
-
@Import
前置知识:
如何将第三方jar包通过Maven坐标引入?
先使用maven命令将第三方jar包安装在本地仓库
mvn install:install-file -Dfile=jar包所在本地磁盘的路径-DgroupId=组织名称-DartifactId=项目名称-Dversion=版本号-Dpackaging=打包方式
再在pom依赖中导入坐标即可。
<dependency><groupId>组织名称</groupId><artifactId>项目名称</artifactId><version>版本号</version>
</dependency>
@Bean的使用
(@Bean只写在方法上,返回的是一个对象,但一般不获取已经在容器中的对象)
使用@Bean注解将第三方的Bean对象注入到IOC容器中,可以在启动类里面去声明一个方法,这个方法里去创建一个对象
再在这个方法上声明一个@Bean注解(将方法返回值交给IOC容器管理,成为IOC容器的Bean对象)。
public class Myconfig {@Bean("User")//相当于XML中的<bean id="user" class="com.lyc.pojo.User"/>//这个方法的名字,就相当于bean标签中的id值//这个方法的返回值,就相当于bean标签中的class属性的值public User getUser(){return new User();//返回要注入到bean的对象}
}
如何证明注入IOC容器成功呢? 将对应类型的对象拿出来就好了。
在springboot项目中的启动方法(run()方法),run()方法会将spring初始化好的容器返回。通过容器来调用getBean方法拿到对应类想的Bean对象。
ConfigurableApplicationContext context = SpringApplication.run(SpringbootMybatisApplication.class, args);
User user = context.getBean(User.class);
System.out.plintln(user)
但是这种方式并不推荐使用。因为在Java代码中类的设计遵循一个单一职责原则,一个类只负责一个功能,启动类就应该只负责启动,不应该再加一个注册的功能。
如果说要配置一个第三方的Bean,建议在配置类中集中注册。就比如:在新增一个config包,里面专门来写配置类。
@Configuration//这个也被spring容器托管,注册到容器中,因为它本身也是一个@Component,表明这是一个配置类,相当于XML当中的 <beans>
@ComponentScan("com.lyc.pojo")//@ComponmentScan是扫描主程序同级的包,所以分层包放在其他地方,就会发生错误
@Import(MyConfig2.class)
public class Myconfig {@Bean("User")//相当于XML中的<bean id="user" class="com.lyc.pojo.User"/>//这个方法的名字,就相当于bean标签中的id值//这个方法的返回值,就相当于bean标签中的class属性的值public User getUser(){return new User();//返回要注入到bean的对象}
}
@Configuration
-
作用:
@Configuration
注解用于定义一个配置类,表示该类中有一个或多个@Bean
方法。@Configuration
是一个特化版的@Component
,它会告诉 Spring 该类是一个配置类,能够包含 Bean 定义方法,并且支持代理机制。 -
使用场景:用于配置类的定义,通常与
@Bean
一起使用,用来显式声明 Bean。在 Spring Boot 项目中,它常用于设置应用的配置,或者在传统的 Spring 项目中配置一些自定义 Bean。
@Configuration
public class AppConfig {@Beanpublic MyService myService() {return new MyServiceImpl();}
}
与 @Component
不同,@Configuration
表明这是一个配置类,其中可以定义多个 @Bean
方法,而这些 Bean 会以一种更为严格和优化的方式加载到 Spring 容器中(通过代理机制)。
注意事项:
-
@Bean只能声明方法。
-
@Bean("名称")//相当于XML中的< bean id="名称" class="对象实体类路径"/ >
-
这个方法的名字,就相当于bean标签中的id值
-
这个方法的返回对象,就相当于bean标签中的class属性的值。
-
如果方法的内部需要使用IOC容器中的Bean对象,只需要在方法上声明即可,spring会自动注入
@Import的使用
常用方式
-
导入配置类(手动扫描)
-
导入ImportSelector 接口实现类
-
@EnableXxxx注解,用于封装@Import注解
举例说明:
当我们的配置类不在启动类的包名下时,启动类是无法扫描到我们的配置类的,我们可以在启动类上添加@Import(配置类的字节码文件),这样就spring就会把配置类文件中对应的Bean对象注入到IOC容器中。
但我们需要导入的配置类过多时,我们可以使用数组的形式,来注入配置类文件,@Import为我们提供了方法。
package org.springframework.context.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Import {Class<?>[] value();} //以数组的方式进行注入
但当我们的配置类文件过多时,以数组形式注入,也会显得代码冗余,如此便用到了我们的第二个方式:导入ImportSelector 接口实现类
-
首先定义一个类去实现ImportSelector接口。
-
重写selectImports方法,在方法中只需要返回一个字符串的数组。而数组中里面的每一个字符串就是要注入到IOC容器中的Bean对象的全类名。
注意事项:springboot会自动调用selectImports方法得到里面数组的内容,然后将这些类的Bean对象自动注入到IOC容器中
代码展示:
public class CommonImportSelector implements ImportSelector{
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata){List<String> imports = new ArrayList<>();return new String[]{"com.lyc.config.CommonConfig"};
}
}
这时只需要在启动类上声明@Import(CommonImportSelector.class)即可。
注意事项:在实际开发中,数组的内容一般不会写死,,基本上都是从配置文件中读取出来的。这样就降低了代码的耦合性,是代码之间更加灵活,程序员只需要将全类名,写入到配置文件中即可。
代码展示:
common.imports
# 一行写一个全类名即可
com.lyc.config.CommonConfig
CommonImportSelector.java
public class CommonImportSelector implements ImportSelector{@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata){//读取配置文件内容List<String> imports = new ArrayList<>(); //初始化数组Inputstream is = CommonImportSelector.class.getClassLoader().getResourceAsStream("common.imports");//通过反射拿到class对象在调用方法将配置文件转换为字节流,字节流不适合文本文件的读取,需要将其转换为字符流BufferedReader br = new BufferedReader(new InputStreamReader(is));//将字节流通过转换流装换成字符流,在使用字符缓冲流提高读写性能,String line = null; // 定义String类型的变量try{while((line = br.readLine())!=null){ //逐行读取数据,只要数据不为空,则放在imports数组中imports.add(line);}}catch(Exception e){throw new RuntimeException(e);}finally{if(br != null){try{br.close();//关闭流}catch (Exception e){throw new RuntimeException(e);}}}return imports.toArray(new String[0]);//new String[0] 只起到一个提供类型的作用}}
如何在进一步,让代码显得更简洁呢?
在启动类中定义一个组合注解,将@Import注解组合一下,在启动类中添加组合注解即可
package com.lyc.springbootmybatis.annotation;import org.springframework.context.annotation.Import;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Import(CommonImportSelector.class)public @interface EnableCommonConfig {}
然后再在启动类上声明@EnableCommonConfig即可,这样的优点是提高了代码的安全性,缺点就是别人不好维护。
在springboot中的源码就有很多这样的EnableXxxx注解,说明在实际开发中这种方法被大众所接受
注册条件
注入到IOC容器中的Bean对象如何插入数据?
-
可以在新建对象时为对象赋值,之后再进行注入。
这种方法是可以插入数据,但是又是和Java代码高度耦合,修改代码是极其繁琐,因此不建议这样做。
-
也可以将对象属性值写入配置文件,再通过@Value导入配置文件,实现与Java代码的解耦。
代码展示:
application.yml
User: name: nihaoage: 19
public class Myconfig {@Bean("User")public User getUser(@Value("${User.name}") String name,@Value("${User.age}") int age){User user = new User();user.setName(name);user.setAge(age);return new User();//返回要注入到bean的对象}}
当我们将配置文件注释之后,再次运行,结果发现,程序报错,和我们的设想不一致,我们设想,有配置文件则选择注入,没有配置文件,就不去注入即可,那么如何去实现?
解析:
springboot提供了设置注册生效条件的注解@Conditional
可以借助这个注解来设置并注册条件,该注解使用较为繁琐,因此springboot官方提供了不少衍生注解来使用,
常用衍生注解:
注解 | 说明 |
---|---|
@ConditionalOnProperty() | 配置而文件中存在对应的属性时,该注解声明下的Bean才会被注册 |
@ConditionalOnMissingBean() | 当不存在当前类型的Bean时,该注解声明下的Bean才会被注册 |
@ConditionalOnClass() | 当前环境存在指定的类时,该注解声明下的Bean才会被注册 |
这个注解如何解决我们刚才的问题呢?
我们的要求:如果配置文件中配置了指定的信息,则注入,否则不注入。
代码展示:
public class Myconfig {@ConditionalOnProperty(prefix = "User",name = {"name,age"})@Bean("User")public User getUser(@Value("${User.name}") String name,@Value("${User.age}") int age){User user = new User();user.setName(name);user.setAge(age);return new User();//返回要注入到bean的对象}}
测试结果:当将配置文件注释之后,User对象并没有被注入到IOC容器中
当配置文件没有被注释时,User Bean对象被注入到IOC容器中
需求:如果IOC容器不存在User,则注入Employee对象,否则不注入
public class Myconfig {@ConditionalOnProperty(prefix = "User",name = {"name,age"})@Bean("User")public User getUser(@Value("${User.name}") String name,@Value("${User.age}") int age){User user = new User();user.setName(name);user.setAge(age);return new User();//返回要注入到bean的对象}@Bean("employee")@ConditionalOnMissingBean(User.class)public Employee getEmployee(){return new Employee();}}
测试结果:当将配置文件注释之后, @ConditionalOnProperty(prefix = "User",name = {"name,age"})注解检测不到属性,则不将User对象注入到IOC容器中,又 @ConditionalOnMissingBean(User.class) 注解检测到User对象在IOC容器中不存在,则将employee对象注入IOC容器。
当配置文件没有被注释时,@ConditionalOnProperty(prefix = "User",name = {"name,age"})注解检测到属性,则将User对象注入到IOC容器中,又 @ConditionalOnMissingBean(User.class) 注解检测到User对象存在与IOC容器,则不将employee对象注入IOC容器。
案例需求:
如果当前环境中存在DispatcherServlet类,则注入Employee,否则不注入。(如果项目中引入了web依赖,则环境中存在DispatcherServlet,否则没有)
@ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")public Employee getEmployee(){return new Employee();}
当存在DispatcherServlet 类时,则将Employee对象注入到IOC容器中。如果不存在,则不注入。
以上就是Bean管理的详细内容,希望对大家有所帮助!