1,概念
1)IOC(Inversion of Control,控制反转)
没有IOC之前,对象A依赖于对象B需要A主动创建B或者使用已经创建的B,控制权在A上。
引入IOC之后,对象A与对象B之间不直接关联,B通过注解@Compoent将自己注入IOC容器中,运行时由IOC容器主动创建一个对象B注入到A要用到的地方。前后比对,A获得依赖B的过程由主动变成被动,控制权颠倒过来,此谓控制反转。全部对象的控制权上缴给了“IOC容器”,即好莱坞原则:
don‘t call us, we‘ll call you
2)DI(Dependency Injection,依赖注入)
由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。依赖注入是实现IOC的方法。
DI使用接口编程,在对象使用时动态注入。
有3种借本的注入方式:
- 构造函数注入(Contructor Injection)
最简单的依赖注入方式.
public class MovieLister {
private MovieFinder finder;
public MovieLister(MovieFinder finder) {
this.finder = finder;
}
...
}
- setter注入
public class MovieLister {
s...
public void setFinder(MovieFinder finder) {
this.finder = finder;
}
}
- 接口注入
接口注入使用接口来提供setter方法,其实现方式如下。
首先要创建一个注入使用的接口。
public interface InjectFinder {
void injectFinder(MovieFinder finder);
}
class MovieLister implements InjectFinder {
...
public void injectFinder(MovieFinder finder) {
this.finder = finder;
}
...
}
3)IOC容器(spring管理对象)
- map(key,value)里存的各种对象
在xml中配置的bean节点、@repository、@service、@controller、@component - 在项目启动时读取配置文件里面的bean节点,根据全限定类名使用反射创建对象并放入map。
- 接下来在代码里需要用到依赖对象的时候,再通过依赖注入(autowired、resource等注解;xml里bean节点内的ref属性,项目启动时读取xml节点的ref属性根据id注入.)。
4)常用注解
给容器注入组件:包扫描+组件标注注解:
注解 | 场景说明 | 备注 |
---|---|---|
@Component | 通用组件模式 | 当组件不好归类的时候,我们可以使用这个注解进行标注。 Controller、@Service、@Repository都可以称为@Component |
@Controller | Web控制器模式 | 控制层组件,详情见下文 |
@Repository | 数据仓储模式 | 数据访问组件,即DAO组件 |
@Service | 服务模式 | 业务层组件 |
@RestController | 控制层组件,包含@Controller、@ResponseBody | 返回体前面不需要再添加@ResponseBody就可以识别 |
注入bean的注解:
注解 | 场景说明 | 备注 |
---|---|---|
@Autowired | Bean依赖注入,只能按类型注入 | spring提供的注解,可以作用在变量(不推荐)、setter方法、构造函数上。属性required配置为false表示找不到不抛出异常正常启动,注入为null。和@Qualifier一起使用 |
@Inject | 用法和@Autowired一样,按类型注入 | 由JSR-330提供,和@Name一起使用 |
@Resource | Bean依赖注入,默认按名称注入(name属性),也支持按类型注入(type属性) | jdk提供的注解。和@Autowired注解类似,都用来声明需要自动装配的bean,区别在于@Autowired是类型驱动的注入,而@Resource是名称驱动的注入,所以前者存在多bean问题,而后者,只要保证bean命名唯一就不会出现多bean的问题。 |
@Qualifier | 细粒度的@Autowired依赖查找 | 当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定,与@Autowired配合使用 |
@Primary | 让spring进行自动装配的时候,默认使用首选的bean,和@Qualifier一个效果。 | 用于一个接口有多种实现时,有此注解的类会被优先考虑。多个注解会抛异常。 |
@DependsOn | 指当前bean所依赖的bean。 | |
@Lazy | 作用在bean上,表明这个bean延迟加载;作用在方法上,表示这个方法被延迟加载;作用在@Component 注释的类上,表明这个类中所有的bean 都被延迟加载。 | 用于解决循环依赖。 |
1>@Autowired 有三种注入方式
@Autowired
注入(不推荐)
原理:在容器启动,为对象赋值的时候,遇到@Autowired注解,会用后置处理器机制,来创建属性的实例,然后再利用反射机制,将实例化好的属性,赋值给对象。==>可能存在注入为null的情况。
@Autowired
private AService aService;
- 构造器注入(推荐)
spring在4.x版本后就推荐使用构造器的方式的来注入字段。
一个对象所有依赖的对象先实例化后,才实例化这个对象。这样就避免了注入null对象。
//Lombok提供的全构造函数; Spring4.3+之后,constructor注入支持非显示注入方式,构造函数之上不用写@Autowired
@RequiredArgsConstructor
@RestController
public class TestController {
//对象必须为final
private final AService aService;
private final BService bService;
}
官方推荐理由
- 单一职责
当使用构造函数注入的时候,你会很容易发现参数是否过多,这个时候需要考虑你这个类的职责是否过大,考虑拆分的问题;而当使用@Autowired注入field的时候,不容易发现问题- 依赖不可变
只有使用构造函数注入才能注入final- 依赖隐藏
使用依赖注入容器意味着类不再对依赖对象负责,获取依赖对象的职责就从类抽离出来,IOC容器会帮你自动装备。这意味着它应该使用更明确清晰的公用接口方法或者构造器,这种方式就能很清晰的知道类需要什么和到底是使用setter还是构造器- 降低容器耦合度
依赖注入框架的核心思想之一是托管类不应依赖于所使用的DI容器。换句话说,它应该只是一个普通的POJO,只要您将其传递给所有必需的依赖项,就可以独立地实例化。这样,您可以在单元测试中实例化它,而无需启动IOC容器并单独进行测试(使用一个可以进行集成测试的容器)。如果没有容器耦合,则可以将该类用作托管或非托管类,甚至可以切换到新的DI框架。
- setter注入
原理:使用set方法依赖注入时,Spring首先实例化对象,然后才实例化所有依赖的对象。如果合作者无效或不存在spring会抛出异常,这样spring保证一个对象的合作者都是可用的。
@RestController
public class TestController {
private final AService aService;
private final BService bService;
@Autowired
public void setAService(AService aService){
this.aService = aService;
}
@Autowired
public void setBService(BService bService){
this.bService = bService;
}
}
2>@Lazy
主要用于解决循环依赖。对应@Autowired的三种用法:
@Autowired
注入(不推荐)- 构造器注入(推荐)
public DictController(@Lazy AService aService){
}
- setter注入(推荐)
3>@Bean注解
注解 | 场景说明 | 备注 |
---|---|---|
@Scope | 设置Spring容器如何新建Bean实例 | 方法上,得有@Bean |
值 | 说明 |
---|---|
Singleton | 单例,一个Spring容器中只有一个bean实例,默认模式。 Bean 放入 Spring IoC 的缓存池中,BeanFactory管理它的生命周期 |
Protetype | 为每一个Bean提供一个实例,即每次注入都新建一个bean。 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean |
Request | web项目中,给每个http request新建一个bean |
Session | web项目中,给每个http session新建一个bean,session过期则失效 |
GlobalSession | 给每一个 global http session新建一个Bean实例,全局作用域,和Portlet相关,几乎不用了。 |
其它:
application: 定义在ServletContext的生命周期中复用的一个单例对象;
websocket:bean定义在websocket的生命周期中复用的单例对象。
session:表示一个会话。(从客户端浏览器连接服务器开始,到客户端浏览器与服务器断开,这个过程就是一个会话)
- Singleton 作为单例是线程安全的吗?
不是。static不安全的,尽量放threadLocal中。==》如Spring的事务管理器,使用ThreadLocal为不同线程保存不同的connection(有状态的)。
有状态的:会保存数据。这样就会受线程安全影响。
Bean的生命周期及执行顺序:
序号 | 阶段 | 具体 | 说明 |
---|---|---|---|
1 | 处理BeanDefinition | BeanDefinition解析 | 扫描类路径,确定类定义: 1. XML资源、 2. Properties资源、 3. Java注解 |
BeanDefinition注册 | 注册到BeanDefinitionRegistry | ||
BeanDefinition合并 | 变为RootBeanDefinition | ||
2 | Bean实例化(Instantiation) | Bean实例化前 | 对象已实例化,但是属性还未设置,都是null |
Bean实例化 | |||
Bean实例化后 | 解析@Autowired 、@Value 、@Resource 、@Inject 、@PostConstruct 、@PreDestroy 等,并将元信息缓存下来 | ||
3 | Bean属性赋值 | 赋值前 | |
赋值 | 依赖注入:查找被@Autowired 、@Value 、@Resource 标注的方法或属性的元信息并注入 | ||
4 | Bean初始化(Initialization) | Bean Aware接口回调阶段 | 回调Aware方法,比如BeanNameAware、BeanFactoryAware: 1. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。 2. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。 3. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。 |
Bean初始化前 | 1. BeanPostProcessor # postProcessAfterInitialization 2. 执行 @PostConstruct 标注的方法 | ||
Bean初始化阶段 | 执行InitializingBean # afterPropertiesSet() | ||
Bean初始化后 | 执行BeanPostProcessor # postProcessAfterInitialization (Spring 的 AOP 在此处加工实现) | ||
Bean初始化完成 | 启动阶段,初始化所有非延迟单例Bean完成后 执行 SmartInitializingSingleton # afterSingletonsInstantiated ,只调用一次 | ||
5 | Bean销毁 | Bean销毁前 | 1. 执行DestructionAwareBeanPostProcessor # postProcessBeforeDestruction 2. 执行 @PreDestroy 标注的方法 |
Bean销毁阶段 | 1. 执行DisposableBean # destory() 2. 执行自定义销毁方法 不会立刻gc |
5)BeanPostProcessor
用来定制Bean,在Bean初始化阶段执行。
如果有多个BeanPostProcessor ,通过Ordered接口或者@Order注解来指定运行顺序。
场景:
- 通过BeanPostProcessor 实现工厂模式
- AOP的实现。
2,BeanFactory
BeanFactory是所有Spring Bean的容器根接口,给IOC提供了一套完整的规范。BeanFactory实际上是实例化、配置和管理众多bean的容器。
有诸多实现:如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。
常用方法:
方法 | 内容 |
---|---|
getType(String name) | 获取Bean的Class类型 |
getBean(String name) | 从Spring容器中获取对应Bean对象 |
containsBean(String name) | 判断Spring容器中是否存在该对象 |
isSingleton(String name) | 判断Bean对象是否为单例对象 |
1)和ApplicationContext区别
ApplicationContext是BeanFactory的子接口。
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
}
1>ApplicationContext功能
- 继承MessageSource,因此支持国际化;
- 统一的资源文件访问方式;
- 提供再监听器中注册bean的事件;
- 同时加载多个配置文件;
- 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
2>区别
- BeanFactory采用延迟加载形式来注入Bean==>调用时才对Bean进行加载实例化
==>
第一次使用调用getBean方法才会抛出异常。ApplicationContext在容器启动时,一次性创建了所有的Bean==>
容器启动时就能发现依赖是否注入。==》程序启动较慢,占用更多的内存空间。运行较快。 - BeanFactory需要手动注册,ApplicationContext可以自动注册。
2)和FactoryBean区别
FactoryBean:是一个创建bean的工厂,又是一个bean。
其作用类似于@Bean注解,但比其能实现更多复杂的功能,重要方法:getObject(),返回一个bean。
源码如下:
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
//获取bean对象
@Nullable
T getObject() throws Exception;
//bean对象的类型
@Nullable
Class<?> getObjectType();
//生产的bean是否为单例
default boolean isSingleton() {
return true;
}
}
使用场景:
在Spring中有两种类型的bean,一种是普通Bean,一种是工厂Bean,即FactoryBean,一般用于创建比较复杂的bean。
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
在application.xml中注册一个UserFactoryBean:
<bean id="userFactory" class="com.yaliyao.pojo.testBeanPojo.UserFactoryBean"/>
@Test
public void testFactoryBean(){
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml"));
User user1 = xmlBeanFactory.getBean(User.class);
User user2 = xmlBeanFactory.getBean(User.class);
Object user3 = xmlBeanFactory.getBean("userFactory");
//要获取FactoryBean本身,要在前面加&
Object userFactory = xmlBeanFactory.getBean("&userFactory");
System.out.println(user1);
System.out.println(user2);
System.out.println(user3);
System.out.println(userFactory);
System.out.println(user1==user2);
}
运行结果:
User(id=0, username=null, password=null)
User(id=0, username=null, password=null)
User(id=0, username=null, password=null)
com.yaliyao.pojo.testBeanPojo.UserFactoryBean@77846d2c
false
当我们使用第三方框架或者库时,有时候是无法去new一个对象的,
比如静态工厂,对象是不可见的,只能通过getInstance()之类方法获取,
此时就需要用到FactoryBean,通过实现FactoryBean接口的bean重写getObject()方法,
返回我们所需要的bean对象。
3,Aware接口
Bean一般不需要感知到Spring容器的存在,但是在一些场景需要用到容器的特殊功能,通过Spring提供的很多Aware接口可以拿到容器信息,从而代替通过@Autowired注解注入BeanFactory或ApplicationContext,直接使用Bean。
常见场景:
- 同一个类的方法互相调用,让第二个@Transaction修饰的方法注解生效
- AOP方法直接调用导致方法失效
1)实现ApplicationContextAware接口
源码:
public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext var1) throws BeansException;
}
使用方式:
- 启动类 implements ApplicationContextAware 并实现
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
//将ApplicationContext 存储起来
SpringContextUtils.setContext(applicationContext);
}
- 全局对象存储ApplicationContext ,并封装调用方法
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component("myServiceSpringContextUtils")
public class SpringContextUtils {
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
public static void setContext(ApplicationContext context) {
SpringContextUtils.applicationContext = context;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
- 调用:通过getBean方法获取想要的Bean对象
//调用
SynchService synchService = SpringContextUtils.getBean(SynchService .class);
synchService.methodA();