文章目录
Spring IoC 概述
Spring最核心的点应该就是IoC,控制反转。所谓控制反转就是对于类中的依赖,不使用硬编码定义,而是依赖接口,然后使用其他方式定义依赖,在spring中使用的就是依赖注入。在代码上来说,就是不用new来实例化,而是使用xml,注解等进行配置。
这样做的好处是,降低耦合性,可以通过修改配置达到更换依赖的目的,使得代码更加容易维护。
IoC和DI的区别
IoC是控制反转,而DI是依赖注入,IoC可以理解为是目的,目的就是达到控制反转,把对依赖的控制权交给其他手段。而DI则是手段,通过依赖注入来实现控制反转的目的。除了依赖注入,还有其他手段,比如实体自动去发现依赖等。而依赖注入则是实例化时被动的被注入。
Spring IoC总体可理解为:加载解析配置->注册BeanDefation->实例化Bean->使用Bean
Bean可以理解为实例,而BeanDefation则是依赖注入配置的统一数据结构,我们可以从多种资源(javaConfig,XML,注解)加载依赖注入的配置,但是最终都解释成BeanDefation这种数据格式,方便后续统一调用。
依赖注入的过程是在实例化Bean中完成的,在实例化的填充属性过程中,针对依赖进行递归实例化,然后填充到对应的属性中,这就实现了依赖注入。
加载解析配置
可以通过三种手段来配置bean
- xml
例子:属性注入
<bean class='cm.com.UserInfoImpl'>
<property name="propertyName" ref="otherClass">
</bean>
- JavaConfig
使用@Configuration和@Bean注册bean,配置@AutoWrite使用
interface test{}
class testImpl implements test{}
@Configuration
class configClass{
@Bean
public test getTest(){
return new testImpl();
}
}
- 注解
使用@Component配置@Autowired使用,现在最常用的方式
interface test{}
@Component
class testImpl implements test{}
@ComponentScan("指定的扫描位置")
class TestMain{
@Autowired
test testImplObj;
}
注册BeanDefation
无论是何种配置,最终都会被注册成BeanDefation,以便后续的Bean实例化操作。
实例化bean
BeanFactory,ApplicationContext,WebApplicationContext
- BeanFactory是最基础的Bean工厂接口,提供最基础的获取bean方法接口。
- ApplicationContext则是在BeanFactory之上提供了更多了内容,包括国际化,统一资源加载,容器内部事件发布等功能
- WebApplicationContext则是提供了web的一些功能,也就是spring mvc
生命周期
- 实例化bean对象:利用反射创建了一个实例的简单对象,此时还未进行属性依赖注入。(构造函数注入应该已经注入了)
- 注入对象属性:对刚实例化的对象的属性进行注入,首先到Bean缓存池查询是否已经被注册了,如果已经被注册,则直接引用。如果没有,则会递归初始化被调用的实例bean,(构造函数的注入也是一样)。
- 检查和激活Aware
Aware是为了实现bean能够感知bean容器的一些信息,比如说,如果想要在bean中获取bean容器给自己定义的ID,那么可以通过实现BeanNameAware接口来获取信息。
public interface BeanNameAware {
void setBeanName(string name) throws BeansException;
}
@Component
public class User implements BeanNameAware{
private String id;
@override
public void setBeanName(String beanName) {
//ID保存BeanName的值
id=beanName;
}
public String getId() {
return id;
}
}
- BeanPostProcessor前置处理和后置处理
BeanPostProcessor是spring提供给用户的可以干预bean生成的接口,这个接口的两个方法最后都要返回bean,AOP就是利用这个接口对生成的bean进行代理的。
public interface BeanPostProcessor {
//前置处理
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
//后置处理
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
- InitializingBean 和 init-method
InitializingBean接口只提供了一个方法afterPropertiesSet,在设置属性后触发,也就是依赖注入结束后。只要bean实现了这个接口就可以在依赖注入后执行自己的代码
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
如果bean指定了init-method方法,那么也会执行该方法。这里是通过反射调用的
@Bean(destroyMethod = "destory")
- DisposableBean 和 destroy-method
和InitializingBean 和 init-method对应,DisposableBean 和 destroy-method是用来对象销毁后执行的方法。可以通过实现DisposableBean接口
public interface DisposableBean {
void destroy() throws Exception;
}
或者指定销毁方法名
@Bean(destroyMethod = "destory")
这些方法在bean被实例化前就被注册,只不过等到对象销毁时才被调用。
作用域
单例和原型
BeanFactory提供了两个原始的作用域,分别是单例和原型,默认是单例,模式,可以通过
@scope(“prototype”)
指定为原型模式。单例模式只会创建一次,之后所有的依赖引用都是直接引用。而原型模式则会每次引用时会复制一个实例,而不是使用原来的对象。
不过如果一个实例为非单例模式,那么其调用方也必须是非单例模式才能生效。也就是单例模式下的实例依赖一个非单例模式是不会生效的,因为单例模式只创建一次,当然不会去修改它的属性。
那么有没有方法能够不用修改调用方却能够实现每次都是生成新的实例呢?
可以使用方法注入
@Service("shoppingCartProvider")
public class ShoppingCartProvider implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
public ShoppingCart getInstance() {
return (ShoppingCart) context.getBean("shoppingCart");
}
}
通过实现ApplicationContextAware接口,(还记得上文生命周期中的aware吗?),可以获得创建该实例的应用上下文。然后直接通过上下文去获取想要的实例,当shoppingCart被设置为原型后,每次调用getInstance方法都是返回一个新的实例。
WebApplicationContext提供的新作用域
对于web应用,WebApplicationContext提供的新作用域提供了三种新的作用域
- request: 每次请求都会创建一个新的实例
- session: 同一个seesion会使用同一个实例
- globalSession: 这个作用域在spring5已经被移除了,用于portlet应用程序,作用范围和session差不多
解释一下portlet是什么。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与servlet不同,每个portlet都有不同的会话。在这种情况下,Spring提供了一个名为global-session的作用域。通过它,一个bean可以通过应用程序中的多个portlet共享。
循环依赖问题
考虑这样一种情况,A依赖于B,所以在A创建完成前必须先实例化B,而B又依赖于C,C又依赖于A。这样就会导致循环实例化。
为了解决这个问题,spring保存了开始实例化但是还未实例化的实例对象。比如A开始实例化,还未完成前,就把A存放到一个Map中,当C实例化时,会先去该Map中查询是否已经存放A,如果存在A,则不会继续实例化A,而是直接引用了Map中的A。
当A实例化完成后,会从该Map中移除,并加入到Bean池中。
spring 使用的设计模式
spring使用了很多设计模式,总结下
- 工厂方法:使用BeanFactory创建Bean实例
- 单例模式:默认的Bean作用域,全局仅一个实例
- 原型模式:原型模式的作用域,原型模式会在创建实例时通过复制已有的实例去创建对象,而不需要重新实例化。
- 模板方法:bean的生命周期就是模板方法,定义好各类方法,按照生命周期的顺序执行。
- 代理模式:AOP使用代理模式进行切面拦截
相关的注解
装配Bean的注解
@AutoWrite:按照类型装配,即找到实现这个类型的bean
@Qualifier(“name”),按名称装配,和@AutoWrite配合可以实现同时按名称和类型匹配
@Resource(type=xx.class,name=“xxx”),可以同时指定类型和名称,相当于@AutoWrite和@Qualifier一起使用
配置bean
@Name(""):用于指定bean的name,以便上面的注解使用
@Configuration和@Bean配合使用配置bean,@Configuration包含在@SpringBootApplication中。
interface test{}
class testImpl implements test{}
@Configuration
class configClass{
@Bean
public test getTest(){
return new testImpl();
}
}
@ComponentScan:指定扫描的位置,包含在 @SpringBootApplication中。
@Component,@Service,@Repository:标注该类为组件,之间并无区别
@Profile 根据配置的Profile,指定在哪种配置下才会实例化
@Conditional:有条件的实例化
@Configuration
class configClass{
@Bean
@Conditional(MathConfig.class) //指定匹配的类
public test getTest(){
return new testImpl();
}
}
import org.springframework.context.annotation.Condition;//注意这里不要import错了
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
class MathConfig implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return false;//只有结果返回true才会实例化
}
}
@Primary:优先实例化,如果有多个符合条件的Bean,那么标注了@Primary将优先被实例化,如果符合条件的有多个都标注了@Primary,那么将会抛出错误