目录
导入 ImportBeanDefinitionRegistrar 实现类
@Resource 与 @Autowired 与 @Qualifier
1、已经知道 @ImportResource 导入Spring 配置文件 如以前经常在 beans.xml 配置的各种定时器、自己写的类以及各种第三方如 MyBatis 与 Hibernate 的类实例等,但 Spring 官方并不建议这么做,因为现在主流是面向全注解编程。
2、所以现在就用 @Configuration(配置类)来替代以前的 Spring 配置文件,用 @Bean 来替代 Spring 配置文件中 <bean> 标签,来给容器中添加组件
@Configuration 配置类
1)@Configuration 指明当前类是一个配置类,相当于把该类作为 spring 的 xml 配置文件中的 。
2)被 @Configuration 注解的类内部包含有一个或多个被 @Bean 注解的方法,这些方法将会被 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类进行扫描,并用于构建bean定义,初始化Spring容器。
3) @Configuation 等价于 <beans> ,@Bean 等价于 <bean>。
4)@Configuration 配置类同样必须是在 SpringBoot能扫描到的位置。
@Bean 添加组件
1)@Bean 是一个方法级别上的注解,配合 @Configuration 注解使用,也可以用在 @Component 注解的类里。
2)默认情况下 bean 的名称和方法名称相同,也可以使用 name 属性来指定,值是一个字符串数组,可以指定多个值。
3)autowire 属性有 3 个值,Autowire.NO:默认装配方式,Autowire.BY_NAME:根据名称装配,,Autowire.BY_TYPE:根据类型装配
4)@Bean 标注的方法在应用启动时就会自动执行,可以 System.out.println() 打印查看。
使用举例
1、提供一个接口与其两个实现类:
//兵器接口
public interface Weapon {
String showInfo();
}
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wmx.web_app.service.Weapon;
//刀。业务层实现类此时并没有加 @org.springframework.stereotype.Service 注解
public class Knife implements Weapon {
@Override
public String showInfo() {
JsonNodeFactory nodeFactory = JsonNodeFactory.instance;
ObjectNode objectNode = nodeFactory.objectNode();
objectNode.put("code", 10011);
objectNode.put("name", "血饮狂刀");
return objectNode.toString();
}
}
//枪、矛
public class Spear implements Weapon {
@Override
public String showInfo() {
JsonNodeFactory nodeFactory = JsonNodeFactory.instance;
ObjectNode objectNode = nodeFactory.objectNode();
objectNode.put("code", 10010);
objectNode.put("name", "火尖枪");
return objectNode.toString();
}
}
2、提供配置类 @Configuration 与 @Bean 装配自己需要的对象:
/**
* @Configuration 指明当前类是一个配置类;相当于把该类作为 spring 的 xml 配置文件中的 <beans>
* @Configuration 配置类可以有多个
*/
@Configuration
public class SystemConfig {
/**
* "@Bean" 相当于在 beans.xml 中的配置:<bean id="knife" class="com.wmx.web_app.service.impl.Knife"/>
* 将方法的返回值添加到容器中,容器中这个组件默认的 id 就是方法名
*/
@Bean
public Knife knife() {
return new Knife();
}
@Bean
public Spear spear() {
return new Spear();
}
/**
* 同一个实例可以装配多个到容器中,可以根据改变方法名来区分,或者直接使用 name 属性指定组件名称
* 这在某些时候是非常有用的,比如装配2个 org.springframework.web.client.RestTemplate,一个使用负载均衡,一个不使用负载均衡
*/
@Bean(name = {"spearGold"})
public Spear spearGold() {
return new Spear();
}
}
3、提供一个控制层方便从浏览器发请请求:
//兵器控制层
@RestController
@RequestMapping("weapon")
public class WeaponController {
@Autowired
private Knife knife;
@Resource
private Spear spear;
@Resource(name = "spearGold") //指定组件名称注入
private Spear spearGold;
/**
* http://192.168.2.77:8080/weapon/info?type=1
* http://192.168.2.77:8080/weapon/info?type=2
* http://192.168.2.77:8080/weapon/info?type=3
*
* @param type :1 表示枪,2表示刀
* @return
*/
@GetMapping("info")
public String info(@RequestParam long type) {
String message = null;
if (type == 1) {
message = spear.showInfo();
} else if (type == 2) {
message = knife.showInfo();
} else {
message = spearGold.showInfo();
}
System.out.println(message);
return message;
}
}
@Import 添加组件
1、使用 @Import 注解,可以将以下内容导入到Spring容器,@Import等同于之前的<import/>标签。:
配置类(Configuration Class) | 1、通过导入其他的配置类,可以将其内部定义的Bean注册到容器中。 2、比如某个配置类(@Configuration)无法被扫描到,此时可以进行导入。 |
普通类(Non-configuration Class) | 1、将普通的类作为Bean导入到Spring容器中。 |
2、@Import 注解可以标注在类上,可以结合 @Configuration 注解、ImportSelector、ImportBeanDefinitionRegistrar 一起使用,并且要在 Springboot 扫描范围内,@Import 也可以直接标记在启动类上。如果结合 @Conditional 注解,还可以实现条件导入。
3、@Import 引入方式也很简单,被引入的类会被实例化 bean 对象,交于 Spring 容器管理,在需要使用的地方直接引入即可。
// 第一步
public class A {
}
// 第二步
@Import({A.class})
@Configuration
public class TestConfiguration {
}
// 第三步
@Autowired
private A a;
。
导入 ImportSelector 实现类
1、ImportSelector 是一个接口,实现这个接口需要重写 selectImports 方法。方法会返回一个String数组,它包含的元素是需要被导入到容器中的类的全限定名。
public class HelloImportSelector implements ImportSelector {
/**
* 返回需要导入的类名的数组,可以是任何普通类,配置类(@Configuration、@Bean、@CompontentScan等标注的类)
* @importingClassMetadata:用来获取被@Import标注的类上面所有的注解信息
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List list = new ArrayList<>();
list.add("intl.skw.test.HelloImport");
return StringUtils.toStringArray(list);
}
}
@Import(HelloImportSelector.class)
public class SpringTestApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringTestApplication.class);
HelloImport helloImport = applicationContext.getBean(HelloImport.class);
helloImport.test();
}
}
导入 ImportBeanDefinitionRegistrar 实现类
1、当类实现了 ImportBeanDefinitionRegistrar 接口,就可以拿到注册器,可以向容器中注册任何的Bean,并且可以对Bean添加属性。
public class HelloImportDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(HelloImport.class)
.getBeanDefinition();
registry.registerBeanDefinition("HelloImport", beanDefinition);
}
}
@Import(HelloImportDefinitionRegistrar.class)
public class SpringTestApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringTestApplication.class);
HelloImport helloImport = applicationContext.getBean(HelloImport.class);
helloImport.test();
}
}
@ImportResource 导入Spring 配置文件
@Resource 与 @Autowired 与 @Qualifier
1、@Resource、@Autowired 都可以标注在成员属性上,或属性的 setter 方法上。
2、@Autowired 注解按类型转配 bean,有一个 required 属性,表示必须找到匹配的 Bean,否则抛异常,默认值为 true,@Autowired(required=true) ,如果允许 null 值,可以设置它 required 属性为 false
3、@Autowired 可以与 @Qualifier 组合使用,添加限定符,解决按类型匹配找到多个 Bean 问题:
@Autowired
@Qualifier("circular") // circular 为 bean 的名称。表示如果按 Shape 类型匹配了多个时,则使用名称为 circular 的对象
private Shape shape; // 通常如果 Shape 接口有多个实现类被交由容器管理了,则此时需要使用 @Qualifier 进行限定
4、@Resource 先按名称装配 bean,当找不到名称匹配的 bean 时,再按类型装配。常用属性为 name,还有其它几个不常用的属性 lookup、type、mappedName、description 等。
@Resource //未指定 name 属性时, 默认使用成员属性的变量名,当找不到名称匹配的 bean 时,再按类型装配
private Circular circular;
@Resource(name = "rectangle") //指定被注入的 bean 的名称,当找不到名称匹配的 bean 时,不再按类型装配
private Rectangle rectangle;
使用举例
1、提供一个接口 Shape(图形),两个实现类 Circular(圆形)、Rectangle(矩形)代码如下:
//图形接口
public interface Shape {
//画图,绘图,绘制
void draw();
}
import com.wmx.web_app.service.Shape;
import org.springframework.stereotype.Service;
//矩形
@Service
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形.");
}
}
//圆形
@Service
public class Circular implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形.");
}
}
2、提供一个控制层从浏览器访问测试:
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wmx.web_app.service.Shape;
import com.wmx.web_app.service.impl.Circular;
import com.wmx.web_app.service.impl.Rectangle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("shape")
public class ShapeController {
@Autowired //按 Rectangle 类型注入
private Rectangle rectangle;
@Resource //先按名称(circular)注入,找不到时按 Circular 类型注入
private Circular circular;
@Autowired
@Qualifier("circular") // circular 为 bean 的名称。表示如果按 Shape 匹配了多个类型时,则使用名称为 circular 的对象
private Shape shape;
@Resource(name = "rectangle") // 因为 Shape 接口有 2 个实现,所以必须指定特定名称
private Shape shape2;
//绘制图形。flag=1为矩形,flag=2为圆形。http://192.168.2.77:8080/shape/draw?flag=1
@GetMapping("draw")
public String draw(@RequestParam int flag) {
JsonNodeFactory nodeFactory = JsonNodeFactory.instance;
ObjectNode objectNode = nodeFactory.objectNode();
objectNode.put("code", 200);
objectNode.put("flag", flag);
if (flag == 1) {
rectangle.draw();
objectNode.put("message", "矩形绘制");
} else if (flag == 2) {
circular.draw();
objectNode.put("message", "圆形绘制");
} else {
objectNode.put("code", 500);
objectNode.put("message", "参数值错误");
}
shape.draw();//输出"绘制圆形."
shape2.draw();//输出"绘制矩形."
return objectNode.toString();
}
}
@Qualifier 标记在方法参数前
/**
* Springboot + JdbcTemplate 多数据源配置
* 1、自定义 {@link DataSource}
* 2、自定义 {@link JdbcTemplate}
*
* @author wangMaoXiong
* @version 1.0
* @date 2022/2/23 17:06
*/
@Configuration
public class DataSourceConfig {
/**
* 默认数据源
* bean:name 属性用于指定实例的名称,同时添加到 Spring 容器中。不指定名称时,默认是与方法名称相同。
* Primary:当使用 @Resource、@Autowired 注入时,如果出现多个相同类型,则选择 primary 标记的类型优先注入。
* ConfigurationProperties:为实例注入全局配置文件中指定前缀下的配置信息
*/
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.default")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
//workflow 数据源
@Bean(name = "workflowDataSource")
@ConfigurationProperties(prefix = "spring.datasource.workflow")
public DataSource workflowDataSource() {
return DataSourceBuilder.create().build();
}
//element 数据源
@Bean(name = "elementDataSource")
@ConfigurationProperties(prefix = "spring.datasource.element")
public DataSource elementDataSource() {
return DataSourceBuilder.create().build();
}
//使用自定义的数据源重新创建 JdbcTemplate 实例,参数 DataSource 此时默认取的是上面 Primary 标记的实例
@Bean(name = "jdbcTemplate")
public JdbcTemplate defaultJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
//使用自定义的数据源重新创建 JdbcTemplate 实例,使用 Qualifier 获取指定名称的 DataSource 实例
@Bean(name = "workflowJdbcTemplate")
public JdbcTemplate workflowJdbcTemplate(@Qualifier("workflowDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
//使用自定义的数据源重新创建 JdbcTemplate 实例,使用 Qualifier 获取指定名称的 DataSource 实例
@Bean(name = "elementJdbcTemplate")
public JdbcTemplate elementJdbcTemplate(@Qualifier("elementDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
@Primary 指定主实现类
1、@Primary 注解同样可以用来处理一个接口多个实现类的值注入情况,它直接写在实现类上,标识如果出现多个类型时,选择此类型优先注入。
/**
* 圆形
* @Primary 注解标识此类为 Shape 接口的主实现,当根据类型注入 Shape 有多个子类型时,选择本类进行注入
*/
@Service
@Primary
public class Circular implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形.");
}
}
2、现在注入 Shape 时,@Resource 可以不再需要 name 属性指定 bean 的名称,@Autowired 也不需要借助 @Qualifier 注解指定 bean 的名称,因为它默认会使用 Circular 进行注入。
@Resource
private Shape shape3;
@Autowired
private Shape shape4;
注入同一类型全部实现类的实例
1、@Resource 与 @Autowired 通常用于获取单个实例,而有时候却希望获取某个接口所有实现类的实例。
2、使用方式也非常简单,就行文件上传一样,只需要修改一下类型就行,使用 Map<T>、List<T> 、数组注入即可。
// 将 DataSource 接口所有实现类的实例注入到属性当中
// 如果是 List 或者数组,则自动把实例对象全部放进去
// 如果是 Map,则把实例的名称赋值作为 key,实例对象作为 value。
// Resource 与 Autowired 接收方式相同
@Resource
private Map<String, DataSource> dataSourceMap;
@Autowired
private List<DataSource> dataSourceList;
@Autowired
private DataSource[] dataSources;
// 当 Bean 初始化时执行 PostConstruct 标记的方法
@PostConstruct
public void init() {
int j = 1;
for (Map.Entry<String, DataSource> entry : dataSourceMap.entrySet()) {
// 1:dataSource=HikariDataSource (null)
// 2:workflowDataSource=HikariDataSource (null)
// 3:elementDataSource=HikariDataSource (null)
System.out.println(j++ + ":" + entry.getKey() + "=" + entry.getValue());
}
}