SpringBoot系列: 理解 Spring 的依赖注入(二)
2021-07-16 01:07
标签:ext.get 实现 rri web qualifier art 带来 注意 contains ============================== 一. @Component 和它的子注解是用来注解 Class 的. 这些注解标注 Bean 的命名规则是: 二. @Bean 注解往往用来标注一个函数, @Bean 注解标注 Bean 的命名规则是: ============================== 1. @Scope("singleton") 标注的 bean, 表示在 Spring 容器中只创建一个 bean 实例, 一般情况下在应用程序启动的时候, 就已经完成 bean 实例化了. 在程序运行过程中, 每次调用这个 bean 将使用同一个实例. 这也是 Spring 默认的 scope. singleton bean 默认都是在容器创建后即初始化, 基本上等同于启动一个 JVM 后即创建 bean 实例, 如果应用很关注启动时间, 可以在声明 bean 的时候再加一个 @Lazy 注解, bean 实例化将延迟到第一次获取 bean 对象时. 一般情况下, 强烈建议所有的 Singleton bean 不要启用延迟加载, 这样就能在程序启动的时候发现问题. 对于 singleton 类型的 bean, 如何做到线程安全呢? 一般有两个方法: ============================== 显然这些场景, 无法通过 bean 类的构造子实现, Spring 项目中, 我们可以使用下面两种方式实现: 1. 在类成员变量上加标注, 可以省去 setter 方法. 2. 在类的方法上加标注, Spring 自动完成"所有 bean"实参装配. 3. 在类的构造函数上加标注, Spring 自动完成"所有 bean"实参装配. 4. @Autowired 注解标在集合上或数组上的含义: 是用来获取同一个接口的多个实现. 使用 Map 时,key 必须声明为 String,在运行时会 key 是注入 Bean 的 name/id. @Inject 注解和 @Autowired 几乎一样, 其功能比 @Autowired 稍弱一些 (比如没有 required 属性), 所以不推荐使用. @Autowired 和 @Inject 注入 bean 对象的执行路径是: @Resource 注入 bean 对象的执行路径是: 解决方式式在声明 Bean 的时候, 可以再多加一个 @Primary 注解. @Primary 既可和 @Component 搭配使用, 也可以和 @Bean 搭配使用. ============================== @Autowired 的标注对象是成员变量、方法、构造函数. 下面是一个 @Qualifier 用在构造参数实参的示例. ============================== 首先我们需要实现 Spring 的 Condition 接口, 然后在 Config 类中使用 @Conditional + @Bean 组合声明 bean 类型. 加上 @Conditional 之后, 将在 bean 实例化时按实际的条件 evaluate 来确定使用哪个子类. ============================== 二. @Required 三. @DependsOn 四. @Order =============================== SpringBoot系列: 理解 Spring 的依赖注入(二) 标签:ext.get 实现 rri web qualifier art 带来 注意 contains 原文地址:https://www.cnblogs.com/harrychinese/p/spring_ioc2.html
Spring 容器中 Bean 的名称
==============================
声明 bean 有两个方式, 一个是 @Bean, 另一个是 @Component 和它的子类 (包括 @Service/@Controller/@Repository/@Configuration), Spring 容器中 bean 名生成规则分两大类, 分别是:
1. 如果 @Component 指定了参数值的话, 参数值就是 bean 名称.
2. 如果 @Component 未指定参数值的话, bean 名就要看被注解的类名, 如果类名开头是两个或两个大写字母, bean 名同类名完全一致; 如果开头只有一个大写字母, bean 名是类名首字母小写版.
1. 如果 @Bean 指定了 name 参数, 以参数为准.
2. 未指定 name 参数的情况下, 以方法名作为 bean 的名称.
Bean 作用域
==============================
参考: https://www.jianshu.com/p/502c40cc1c41
Bean 常用的作用域有下面 5 类,
@Scope("singleton")
@Scope("prototype")
@Scope("request")
@Scope("session")
@Scope("global-session")
2. @Scope("prototype") 标注的 bean, 每次获取这个 bean 都创建一个新的实例.
3. @Scope("request"), 仅适用于 Web 项目, 给每个 http request 新建一个 Bean 实例.
4. @Scope("session"), 仅适用于 Web 项目, 给每个 http session 新建一个 Bean 实例.
5. @Scope("global-session"), 仅适用于 portal Web 项目, 给每个 global http session 新建一个 Bean 实例.
==============================
不同 scope bean 的线程安全性问题
==============================
Spring 默认的 scope 时 singleton, 因为不需要频繁的对象创建和内存回收, 性能最好, 但需要注意线程安全问题.
prototype 类型的 bean, 每次注入的都是一个全新对象, 显然没有线程安全问题.
request 和 session 对象, 除非我们在同一个 request 或 session 中使用了多线程, 需要注意一下线程安全问题, 在绝大多数情形下, 这两个 scope 类型的 bean 是线程安全的, 同时因为 bean 是有状态的, 方便了 web 应用开发.
1. bean 类中压根不定义成员变量, 这样就杜绝了线程不安全的隐患.
2. 如果 bean 类必须有成员变量, 一定要将成员变量加到 ThreadLocal 中.
或者, 干脆将 singleton scope 改成 prototype.
Bean 对象的初始化钩子
==============================
设想如下场景:
1. 我们需要注入一个 bean 对象, 并要对该 bean 对象做一些特别设置, 而这个 bean 对象类的代码又不归我们管.
2. 对于 Boss 这个对象, 注入很多种 bean 对象 (比如 car/office 等), 如何在一处代码中集中为 car/office 对象做一些特别设置?
1. Spring 提供的 @Bean 注解有 initMethod 和 destroyMethod 属性.
2. JSR-250 定义的标准有 @PostConstruct 以及 @PreDestroy 注解, 这两个注解一般和 @Component 搭配使用.
==============================
@Autowired Bean 注入
==============================
@Autowired 可以对类成员变量、方法和构造函数加注解, 完成自动装配任务.public class Boss {
@Autowired
private Car car;
}
public class Boss {
private Car car;
private Office office;
@Autowired
public void Init(Car car, Office office) {
this.car = car;
this.office = office ;
}
}
public class Boss {
private Car car;
private Office office;
@Autowired
public Boss(Car car, Office office) {
this.car = car;
this.office = office ;
}
}
摘自 https://www.chkui.com/article/spring/spring_core_auto_inject_of_annotationinterface A {}
@Component
class implA1 implements A{}
@Component
class implA2 implements A{}
class MyClass {
@Autowired
private A[] a;
@Autowired
private Set set;
@Autowired
private Map
==============================
JSR-250/JSR-330 的 Bean 注入
==============================
JSR-250 标准的注入注解是 @Resource.
JSR-330 标准的注入注解是 @Inject.
Spring 专有的注入注解是 @Autowired.
在实际项目中, 经常使用到的是 @Autowired 和 @Resource.
@Autowired 默认是 byType 注入的, 而 @Resource 本身有 name 和 type 属性, 默认是按照 byName 注入的.
1.Match by Type : 优先 byType 注入
2.Restricts by Qualifier : 依照 @Qualifier 指定的 name 来注入, 但如果没有注入成功, 直接报错.
3.Match by Name : 最后按照默认的 name 注入.
1. 如果同时指定了 name 和 type, 则在容器中找 name 和 type 都匹配的 bean 进行装配, 找不到则抛出异常.
2. 如果指定了 name, 则在容器中查找名称 (id) 匹配的 bean 进行装配, 找不到则抛出异常.
3. 如果指定了 type, 则在容器中找到类型匹配的唯一 bean 进行装配, 找不到或者找到多个, 都会抛出异常.
4. 如果既没有指定 name,又没有指定 type 时, 将按照下面执行路径:
4.1 按照默认的名字注入.
4.2 按照类型注入.
4.3 依照 @Qualifier 的修饰来注入.
参考:
http://javainsimpleway.com/autowired-resource-and-inject-2/
http://einverne.github.io/post/2017/08/autowired-vs-resource-vs-inject.html
==============================
@Primary 解决多个子类无法注入问题
==============================
有时候在我们的项目中, 同一个接口可能会有多个实现 (或子类), 在使用 @Autowired 注入 bean 实例时, 会报错. 原因很简单, @Autowired 默认是按照 type 注入的, 现在有多个子类, Spring IoC 容器不知道该如何注入了. @Component
@Primary
public class BenzCar implements ICar {
public void run() {
System.out.println("Benz run");
}
@Override
public String toString() {
return "Brand: Benz, price:1000";
}
}
@Component
public class VWCar implements ICar {
public void run() {
System.out.println("VW run");
}
@Override
public String toString() {
return "Brand: Volkwargon, price:1000";
}
}
@Component
public class Boss {
@Autowired
private ICar car;
}
@Configuration
@ComponentScan("javaTestMaven.demo2")
public class SomeConfig {
}
public class App {
public static void main(String[] args) {
App app=new App();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(SomeConfig.class);
context.refresh();
Boss boss = context.getBean(Boss.class);
System.out.println(boss);
context.close();
}
}
@Qualifier 的使用
==============================
上个例子中, 使用了 @Primary 注解来解决多个子类无法注入的情况, 其实 Spring 还提供其他办法, @Qualifier 注解就是其中之一.
一旦加上了 @Qualifier 之后, @Autowired 在 byType 注入失败后, 将按照 @Qualifier 设定的参数来注入. @Qualifier 可以和 @Autowired/ @Resource/ @Inject 一起使用, 但多数情况下是和 @Autowired 一起使用.
@Qualifier 的标注对象是成员变量、方法入参、构造函数入参.
上面表述看上去是好像一样, 其实是有差别的, 重点是两个注解修饰的主体是不同的, 以注解方法为例, @Qualifier 修饰的是单个实参, 而 @Autowired 修饰的是方法体. 如果该方法有多个 bean 类的形式参数, 每一个参数都可使用 @Qualifier 修饰. public class Boss {
private Car car;
private Office office;
@Autowired
public Boss(Car car, @Qualifier("office") Office office){
this.car = car;
this.office = office ;
}
}
@Conditional 实现条件注入
==============================
@Primary 的缺点: 通过类型方式将接口和某个具体实现绑定死了, 好处:我们可以随时切换 Primary 类, 达到切换具体实现.
@Qualifier 的缺点: 通过名称方式将接口和具体实现的名称绑定死了, 好处:我们可以随时调整名称, 达到切换具体实现.
但总体来讲 @Primary 和 @Qualifier 都有点 hard code 味道, 有什么更好的方案呢? 答案就是 @Conditional, 使用起来稍微复杂些, Spring Boot 中大量使用了 @Conditional 注解, 比如多 profile 环境. public class LinuxCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean result=context.getEnvironment().getProperty("os.name").contains("Linux");
return result ;
}
}
public class WindowsCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean result=context.getEnvironment().getProperty("os.name").contains("Windows");
return result ;
}
}
@Configuration
@ComponentScan("javaTestMaven.demo2")
public class SomeConfig {
@Bean
@Conditional(LinuxCondition.class)
public ICar getBenzVWCar() {
return new VWCar();
}
@Bean
@Conditional(WindowsCondition.class)
public ICar getBenzCar() {
return new BenzCar();
}
}
public class App {
public static void main(String[] args) {
App app=new App();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(SomeConfig.class);
context.refresh();
ICar car = context.getBean(ICar.class);
System.out.println(car);
context.close();
}
}
Spring 的其他注解
==============================
一. @Autowired(required=false)
@Autowired() 如果不指定 required 属性, 相当于 required 属性为 true, 意思是被注入的对象不能为 null, 如果设定 required=false, 被注入的对象可以为 null.
因为 @Autowired(required=false) 容许注入 null, 会给程序带来潜在的问题, 所以仅仅在开发和测试阶段使用, 比如被依赖的类还没被声明成 bean.
依赖注入主要是两种方式, 通过构造子注入或通过 Setter 函数注入, 一般地我们会将必需的依赖项通过构造子注入,对于非必需的依赖项通过 Setter 函数注入.
Spring 提供了 @Required 注解, 可以将某个 Setter 注入上升到必需级别.
@Required 用来注解 Setter 方法, 只适用于基于 XML 配置的 setter 注入方式, 效果和 @Autowired(required=true) 一样, 推荐使用后者.
直接依赖关系推荐使用构造子和 Setter 函数设置, 但对于没有直接依赖的对象或依赖关系不明显, 可以使用 @DependsOn.
@Order 一般用在控制多个子类的 bean 对象实例化的顺序, @Order() 注解的参数值越小, 实例化越早.
在下面例子中, lst 中的第一个元素是 implA2 对象, 第二个元素是 implA1 对象. interface A {}
@Component
@Order(2)
class implA1 implements A{}
@Component
@Order(1)
class implA2 implements A{}
class MyClass {
@Autowired
private List lst;
}
参考
===============================
https://www.chkui.com/article/spring/spring_core_auto_inject_of_annotation
https://www.ibm.com/developerworks/cn/java/j-lo-spring25-ioc/
https://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/
https://www.baeldung.com/spring-autowire
上一篇:python常见关键字的使用
文章标题:SpringBoot系列: 理解 Spring 的依赖注入(二)
文章链接:http://soscw.com/essay/105821.html