容器
容器应该能够向该环境中所管理的组件提供一些基本服务:
- 生命周期管理
- 依赖解析
- 组件查找
- 应用程序配置
此外, 如果容器还可以提供下面的中间件服务, 就更好了
- 事务管理
- 安全性
- 线程管理
- 对象和资源池
- 对组件的远程访问
- 通过 JMX 之类的 API 管理组件
- 容器的扩展和定制
依赖注入原则
依赖注入的原则是应用程序对象不应该负责查找他们所依赖的资源或协作者, 而是应该由 IoC 容器处理对象的创建和依赖注入, 从而使资源查找外部化, 将过程从应用程序代码转移到容器
循环引用
A 引用 B,B 引用 A, 先创建 A
先创建 A, 此时发现要 B, 那么去创建 B, 发现又要 A, 那么直接就把创建中的 A 放到了 B 中
如果要引用的 Bean 是创建中的, 那就把这个创建中的 Bean 直接注入到带注入的 Bean 中, 所以 B 中的 Bean A 是创建中的一个 Bean, 是不完整的 Bean
依赖注入的好处
- 可以从应用程序代码中完全删除查找逻辑代码, 依赖项可以以一种可插拔的方式注入到目标组件中.
- 组件的单元测试将会变得非常容易 (以前在代码中查找的时候要先将那些组件给启动了, 现在由容器控制了, 只要启动容器就好了)
- 组件中没有对具体的类的依赖, 所以不需要修改任何代码, 就可以非常容易的针对不同环境进行应用程序的配置 (其实就是 RocketMq 替换为 RabbitMq 不需要对代码进行大规模修改就是了)
- 对容器的 API 没有任何依赖, 可以更换容器 (在使用 spring 的时候, 如果要切换成其他的容器, 只要修改配置文件就好了, 代码中的依赖注入会由新的容器去完成, 侵入性很小了)
- 不需要实现任何特殊的接口, 直接写业务就可以了 (编写的类只是一个普通的类, 如果用到以前的方式-注入查找, 需要继承一个类然后实现它里面的多个回调方法)
两种注入方式的优缺点对比
setter 注入
- 优点:
- 在组件被创建之后可以重新进行配置 ( 因为在创建的时候在类中需要显示的声明 setter 方法, 所以在运行的时候其实是可以由外部调用者主动替换掉这个类的 )
- 可以处理循环依赖, 但某些功能并不能用循环依赖去解决, 因为注入到其中的可能是 postprocess 状态的
- 缺点:
并不是所有所需的依赖都可以在使用前被注入, 从而使组件处于一种部分配置的状态 ( 在一些情况下,setter 方法的调用顺序可能非常重要, 但是该顺序无法在组件约定中表达出来 )
构造函数注入
- 优点:
可以保证容器中每一个被管理的组件都处于一致状态, 在创建之后可以马上使用
代码量相对 setter 要少
- 缺点:
组件创建完毕后就无法对其进行修改了
依赖注入
autowire-candidate 属性
如果一个或多个 bean 都设置 autowire-candidate=”false”, 那么如果在代码中使用 @autowire 的时候就不会有 bean 注入进去了
创建 Bean 的两个阶段
- 处理配置元数据并建立 Bean 的定义, 过程中还会对这些 Bean 定义进行验证 (此时 Bean 没有创建, 属性也没有赋值进去)
- 完成 Bean 的创建, 完成依赖注入 (此时也不是所有的 Bean 都创建了)
- 一个 Bean 在被完全创建且自己的依赖项被注入之前是不会作为一个依赖项被注入到其他 Bean 中的
Bean 被覆盖的两种原因
- 后创建的 bean 覆盖先创建的 bean
- 子 ApplicationContext 中的 Bean 会覆盖掉父 ApplicationContext 中的 Bean
容器中一般有两个 ApplicationContext
一个父, 一个子. 父由 ContextLoadListener 来创建, 子由 DispatcherServlet 来创建
xml 中 setter 注入注意事项
xml 定义 setter 注入 property 后面的 name 一定要是 set 方法搞出来的 bean 的名字
比如说 setAccountDao, 那么 property 后面的 name 一定要是 accountDao,ref 倒是无所谓
获取 ApplicationContext 实例
- 通过 WebApplicationContextUtils
- 通过实现 ApplicationContextAware 接口, 并实现下面的方法, 即可在要使用的地方获得到 ApplicationContext
public void setApplicationContext(ApplicationContext applicationContext)
bean 重名
多个 xml 中定义了多个相同的 bean 名称, 会出现 bean 覆盖的现象, 解决方法可以有两种:
- 使用 name 属性来替代 id, 在 name 属性中可以用逗号来分隔, 除第一个名称之外都称之为 alias
- 使用 alias 标签, 且可以设置多个. 如:
<alias name="accountdaoInMemory" alias="accountDao"/>
Bean 的实例化方法
- setter 注入
- 构造方法注入
- 工厂注入
- 静态方法注入
public static Foo createFoo();
- 普通方法注入
public Foo createFoo();
实现 FactoryBean 接口, 并实现以下方法public Foo getObject(); public Class<?> getObjectType(); //返回一个 Foo.class public boolean isSingleton();
Bean 作用域
- 单例 (singleton) 整个生命周期中只创建一次
- 原型 (prototype) 每一次调用都会创建一个
- 如果在一个单例的 bean A 中每次调用都想要使用一个新的 B, 那么 A 必须设置为 singleton, 而 B 设置为 prototype, 但是 A 在创建的时候就已经将 B 放进去了, 在整个生存期中只会注入这么一次, 所以 A 中的 B 是不变的. 这个时候就要在 A 中通过 Bean 查找的方式来每次创建一个新的 B 实例了
- 如果是在 Web 应用程序中, 还可以使用 Request 和 Session 两种作用域
延迟加载
- 优点: 加快了启动速度
- 缺点: 如果配置错误, 在使用这个 bean 的时候才会发现
xml 中使用 lazy-init 方法
注解中使用 @Lazy
A 引用 B,A 是正常加载,B 是延迟加载, 那么在 A 初始化的时候,B 就算是延迟加载的也会被加载了
生命周期方法
- 使用 init-method
方法名称无所谓, 但是返回值必须是 void
在 bean 创建完成之后,init 被容器调用, 这是 bean 实例已经可以使用了, 所以在 init 方法中可以完成任何工作 (其实之前用的把配置表中的信息放到 redis 中就可以这个时候做了).
而 destory 方法调用的时间就不一定了, 根据作用域的不同, 情况可能会不同:
singleton 时在整个 spring 容器关闭的时候调用 propotype 时这个 bean 在实例化之后不能被跟踪, 所以不会调用 request 时在 web 请求结束时调用 session 时在 Http 会话超时或无效时调用
- 使用 @PostConstruct 和 @PreDestroy
- 实现 InitializingBean 和 DisposableBean 接口并实现方法:
public void afterPropertiesSet(); public void destroy();
转载请注明:热爱改变生活.cn » Spring 的一些回头总结
本博客只要没有注明“转”,那么均为原创。 转载请注明链接:sumile.cn » Spring 的一些回头总结