IOC容器
官网google机翻定义如下
IoC 也称为依赖注入 (DI)。这是一个过程,其中对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖项(即,它们使用的其他对象) . 然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖项的实例化或位置的逆过程(因此得名,控制反转)。
容器的作用是管理bean,处理业务。所以以BeanFactory
开始,以ApplicationContext
为阶段点,接着往下都是具体技术、业务场景实现。
容器执行bean依赖解析如下:
● 使用ApplicationContext描述所有 bean 的配置元数据创建和初始化。配置元数据可以由 XML、Java 代码或注释指定。
● 对于每个 bean,它的依赖关系以属性、构造函数参数或静态工厂方法参数的形式表示(如果您使用它而不是普通构造函数)。在实际创建 bean 时,将这些依赖关系提供给 bean。
● 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个 bean 的引用。
作为值的每个属性或构造函数参数都从其指定格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如 int、long、String、boolean等。
Spring 容器在创建容器时验证每个 bean 的配置。但是,在实际创建 bean 之前不会设置 bean 属性本身。创建容器时会创建单例范围并设置为预实例化(默认)的 Bean。范围在Bean Scopes中定义。否则,仅在请求时才创建 bean。创建 bean 可能会导致创建 bean 图,因为 bean 的依赖项及其依赖项的依赖项(等等)被创建和分配。请注意,这些依赖项之间的解析不匹配可能会出现较晚 — 即,在第一次创建受影响的 bean 时。
循环依赖
场景:先有鸡,还是先有蛋。鸡是蛋的爹,蛋是鸡的妈
错误案例:直接使用构造函数,循环依赖会出现错误。抛出BeanCurrentlyInCreationException
。
解决方案:使用setter函数配置
bean作用域(@Scope)
- singleton 单个容器内唯一
- prototype 数量不限
- request web生命周期有效
- session 单个session会话有效
- application 单个应用上下文有效
- websocket websocket内有效
... 自定义作用域:不会,不推荐。
自定义bean
主要作用是在bean的创建和销毁的时候附加一个钩子函数来进行我们所需要的特定操作
bean回调函数
执行顺序如下:
初始化后执行
- 带有@PostConstruct注释的方法
- 实现InitializingBean接口的afterPropertiesSet()方法(不推荐使用)
- 通过xml自定义配置init()方法
销毁后执行 - 带有注释的方法 @PreDestroy
- destroy()由DisposableBean回调接口定义(不推荐使用)
- 自定义配置destroy()方法
bean继承
没什么好说的,就是java的继承父类,spring多了xml配置方式初始化bean对象。
容器扩展
- 使用
BeanPostProcessor
自定义 Bean - 使用
BeanFactoryPostProcessor
自定义配置元数据 - 使用
FactoryBean
自定义实例化逻辑
注解配置容器
spring针对大家讨论的
注解配置
更好用还是xml配置
给出了极其官方的回答,各有所长,按需使用。双方都不得罪。
注解方式:简洁明了。
xml方式:擅长连接组件,而无需接触它们的源代码或重新编译它们。一些开发人员更喜欢将布线靠近源,而其他人则认为带注释的类不再是 POJO,而且配置变得分散且难以控制。
我来说,xml配置差了点,第一不看源码,你永远只知道复制别人的xml配置。第二点:带注释的类不再是POJO,配置分散难以控制。这在我看来也不是问题,一大堆xml才真的难以控制。一个简单的引用就需要配置一行xml内容。
@Required
该注解已于5.1版本
开始不推荐使用
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
@Autowried
按类型注入,该注解可用于字段
、构造方法
、setter方法
、其他具有任意名称和多个参数的方法
。
使用很简单,唯一要注意的是@Autowired
,@Inject
,@Value
,和@Resource
注释是由Spring通过BeanPostProcessor实现的。所以我们不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有)中应用这些注释。这些类型必须使用 XML 或 Spring@Bean方法显式连接 。
@Primary
使用位置:bean声明位置
使用说明:当存在多个同类型bean时,@Autowried引入会报错,如果有且仅有一个bean的声明使用了该注解,那么不会报错,并且引入该bean。例如以下案例,直接使用@Autowried注解,会引入firstMovieCatalog()返回的bean。
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
@Qualifier
使用位置:bean声明位置,bean引入声明位置。
使用说明:可匹配bean名字,但是强烈建议不这么做(按bean名字注入那是@Resource该做的事),这个注解的作用是搭配@Autowried实现注入经过可定语义限定的bean集合注入。如下案例
public class MenuBeanConfig {
record Menu(String name) {
}
@Bean
@Qualifier("menu-a")
Menu menua1() {
return new Menu("菜单-a-1");
}
@Bean
@Qualifier("menu-a")
Menu menua2() {
return new Menu("菜单-a-2");
}
// 不建议使用案例
@Bean
@Qualifier("menu-b")
Menu menub1() {
return new Menu("菜单-b-1");
}
@Bean
Object test(@Qualifier("menu-a") Set<Menu> menus) {
menus.forEach(System.out::println);
return new Object();
}
}
CustomAutowireConfigurer
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
@Resource
特性是按名字注入。但当未声明需要注入的bean的名字的时候,先按类型找,后按照当前字段名或者当前方法里面的属性名字找
@Value
用来注入配置文件的配置属性。很强大,具体的使用后面单独出一篇说明。
@PostConstruct和@PreDestroy
这两个上面说过,作为生命周期函数,可以在里面做些特定的bean初始化操作
类路径扫描与组件管理
该章节主要说IOC容器里面的bean的来源路径与相关路径管理
组件的声明注解@Component
默认的组件声明注解是@Component
,像@Repository
、@Service
和@Controller
都是基于@Component
的进一步声明。要声明为IOC容器管理的组件,那么一定直接或间接声明了@Component
注解。
组件扫描配置注解@ComponentScan
@ComponentScans只是对@ComponentScan做了个简单的集合封装,不多说。主要是@ComponentScan
注解,通过对其属性的配置,可以适应各种各样的组件扫描场景。
- basePackageClasses 组件扫描包定位class集合。作用为会扫描这些class所在的包
- basePackages 组件扫描包集合
- excludeFilters 包剔除过滤器
- includeFilters 包包含过滤器
- lazyInit 是否懒初始化
- nameGenerator 组件名生成器
- resourcePattern 资源路径匹配规则
- scopedProxy 组件有效范围代理,该配置会冲掉scopeResolver配置
- scopeResolver 组件有效访问解析
- useDefaultFilters 使用默认过滤器
- value 组件扫描包集合
JSR330支持
说是标准注解,但在spring框架内没什么卵用,只是spring对这套标准做了支持,都是些重复的功能。
@Inject
该注解作用等价于@Autowried注解
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
@Named
作为注入配置注解,作用和@Qualifier类似;作为组件声明注解,作用等价于@Component
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
@ManagedBean
作用等价于@Component注解
特定功能缺失
使用JSR 330
标准注解,相对于spring注解而言,会缺失些特定的功能,懒得总结了。请看原文https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-standard-annotations-limitations
环境配置
@Bean
声明一个Bean
@Profile
指定环境生效,以下配置表明配置在spring.profiles.active=development
时生效
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
附加功能
国际化MessageSource
没什么乱用,什么年代了,基本都是前端搞了。后端存的数据,不是字母,就是数字。具体显示经过一层转换。这一层就是国际化。
标准事件和自定义事件
事件+监听,标准的观察者模式。在容器内部声明了一个或多个监听某一个事件的监听器,后续有人在这个容器内发布了这个事件,那么会触发监听这个事件的所有监听器。
内定的事件
看名字就知道大概意思,不多说
- ContextRefreshedEvent
- ContextStartedEvent
- ContextStoppedEvent
- ContextClosedEvent
- RequestHandledEvent
- ServletRequestHandledEvent
自定义事件实现
分三个:事件、监听者、发布者
- 实现
ApplicationEvent
接口,创建自定义事件 - 实现
ApplicationListener
,创建自定义监听者 ApplicationEventPublisher
容器事件发布者
具体代码实现可以通过注解的方式灵活使用。如下代码:
// 常规监听
@EventListener
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
// 多种事件监听
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
// 监听Spel语法匹配
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
// 异步事件监听
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}
// 配置监听器执行顺序
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
便捷的访问容器资源
资源标准是ResourceLoader
,而ApplicationContext
继承了该接口,那么我们通过ApplicationContext
的具体实现就拥有对容器内资源的操作API。
应用程序启动跟踪
web应用手动ApplicationContext
实例化
将ApplicationContext
打包成Java EE RAR
这三个都没用过,不好评价。主观感觉对于目前的微服务,前后分离架构模式没什么乱用。
bean工厂
官网扯了下BeanFactory
和ApplicatonContext
的区别与BeanFactory
的不足,简而言之就是一句话,能用ApplicatonContext
的就别用BeanFactory
。