目录
什么是Spring IOC?Spring IOC的原理是什么?如果你要实现IOC要怎么做?
Spring中BeanFactory和ApplicationContext的区别是什么?
请谈一谈Spring中自动装配的方式有哪些?
自动装配即spring容器来帮我们进行bean的匹配和依赖注入(装配包含类型匹配和依赖注入,这是按实际功能进行的划分,我觉得这样更能理解两者关系),总的来说有两种方式:
- 按名称装配:在容器中查找与依赖属性名字相同的bean进行注入。
- 按类型装配:在容器中查找与依赖属性类型相同的bean进行注入。
在具体的实现上,可以通过Xml配置文件或注解来完成。
1. 如果通过xml配置文件实现,需要在<bean/>标签中设置autowire属性,有四个可选项:no、byName、byType、constructor。
- no:不进行自动装配,必须手动设置ref属性指定注入对象。
- byName:按名称装配。
- byType:按类型装配。
- constructor:构造器注入,本质上还是按类型装配,容器会根据构造器参数的类型来匹配,如果与依赖属性的类型相同且容器中有这个bean,就在对象实例化的时候通过这个构造器注入。
2. 如果通过注解来实现,可以选择@Autowired或@Resource注解,与xml中的byName和byType选项是一个道理。
- @Autowired:按类型装配,可以放在属性、setter方法或构造器上。
- @Resource:按名称装配,可以放在属性和setter方法上。
说了这么多,总结起来就是:自动装配可以分为按名称装配和按类型装配。具体实现可以通过配置xml文件或者使用注解来实现。
依赖注入有哪几种方式?以及这些方法如何使用?
- setter注入。通过类属性对应的setter方法来注入。
- constructor注入。通过构造器注入,构造器的参数类型必须与属性类型一致。
Xml配置文件:
Java Bean:
setter注入发生在bean实例化之后,constructor注入发生在bean实例化之前。上面Info是通过setter方法注入的,HelloSpring是通过constructor方法注入的。
请问Spring中Bean的作用域有哪些?
在spring5.x中,作用域有:
- singleton:bean在容器中是单例存在的。
- prototype:每次从spring容器中获取bean时都会创建一个新的实例。
- request:每次http请求都会创建一个新的实例。仅在web环境下有效。
- session:在每个http session的生命周期里,会创建一个新的实例。
- application:在整个web生命周期里仅创建一个新的实例。
- websocket:在websocket生命周期里仅创建一个新的实例。
注意:request、session、application和websocket作用域只有在web环境中才有效(使用web ApplicationContext,如XmlWebApplicationContext)。如果在非web环境下(如使用ClassPathXmlApplicationContext)使用这些作用域,会抛出IllegalStateException。
什么是Spring IOC?Spring IOC的原理是什么?如果你要实现IOC要怎么做?
1. 什么是Spring IOC?
IOC是Inversion of Control的缩写,意思是控制反转。传统上,对象是由程序员编写程序代码直接操控的,控制反转就是反过来把这些对象的操控权交给容器,通过容器来实现对象组件的装配和管理,由容器来创建对象并管理对象之间的依赖关系。Spring IOC就是由spring来负责控制对象的生命周期和对象间的关系。
2. Spring IOC的原理是什么?
IOC主要是获得依赖对象的过程被反转了,就是获得依赖对象的过程由自身管理变为了由IOC容器主动注入,所以Spring IOC中最主要的就是依赖注入,Spring IOC的原理可以引申为DI的原理。
比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
1. 通过xml配置文件进行属性注入:就是通过反射拿到待注入属性的Field,调用Field.set()来实现注入。
2. 通过注解来实现注入:判断注解是放到属性上还是方法上,然后通过反射进行注入。
举个简单的例子,我们找女朋友常见的情况是,我们到处去看哪里有长得漂亮身材又好的女孩子,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。(比如类A要使用类B,类B要使用类C,如果你在对象A创建之后就能使用完整功能,必须在实例化对象A之前将创建好的对象B注入到A中,同理也必须在实例化对象B之前将创建好的对象C注入到B中......这里只是一个类依赖另一个类的情况,如果A不仅仅依赖于B,还依赖于D、E、F呢?B不仅仅依赖于C,还依赖于G、H、I呢?那岂不是非常复杂了?所以容器帮我们解决了这个问题)
Spring中BeanFactory和ApplicationContext的区别是什么?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
-
继承MessageSource,因此支持国际化。
-
能自动注册BeanPostProcessor和BeanFactoryPostProcessor。
-
统一的资源文件访问方式。
-
提供在监听器中注册bean的事件。
-
同时加载多个配置文件。
-
提供了更加完整的生命周期管理。
下面是Spring 5.2.7官方文档的说明:
@Autowired和@Resource之间的区别
@Autowired可用于:构造函数、成员变量、Setter方法;@Resource可以用于类、成员变量、setter方法。
@Autowired和@Resource之间的区别
-
@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
-
@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
谈谈Spring Bean的生命周期?
Spring Bean的生命周期可以分为五个阶段:
- 加载
- 实例化(Instantiation)
- 属性赋值(Polupate)
- 初始化(Initialization)
- 销毁(Destruction)
加载阶段,即使当Spring容器被创建的时候,会根据xml配置文件或Spring配置类来获取配置的bean信息,并将这些bean解析成BeanDefinition,存入到一个map中,key是beanName,value就是BeanDefinition。
实例化阶段,就是通过构造器或者工厂方法(xml中factory-bean指定工厂类,factory-method指定前面工厂类中的工厂方法,通过工厂方法来创建该bean对象)来实例化所有单例且非懒加载的bean(非单例的bean会在使用时实例化如getBean())。在实例化操作的前后会遍历所有的BeanDefinition,检查其是否实现了InstantiationAwareBeanPostProcessor接口,是则执行这个接口的前置(postProcessBeforeInitialization)和后置(postProcessAfterInitialization)方法。
属性赋值阶段,会根据名称或者类型使用反射进行依赖注入。
初始化阶段,主要是执行自定义的初始化方法(如在xml配置文件中设置的init-method属性),如果bean实现了InitializingBean接口,还会调用bean的afterPropertiesSet()方法,实现InitializingBean接口的目的是为了对“bean的属性设置完成”这个事件做出反应。在初始化阶段前后,会遍历所有的BeanPostProcessor,并执行前置(postProcessBeforeInitialization)和后置(postProcessAfterInitialization)方法。同时在初始化之前也会执行所有的aware对象的相关方法,有一些aware(如BeanNameAware)是直接执行,有一些(如ApplicationContextAware)是通过ApplicationContextAwareProcessor接口的before方法执行,这个接口也是BeanPostProcessor的子接口,它会检查当前bean是否是Aware接口的实现类,是的话就执行Aware接口的特有方法。
请别再问Spring Bean的生命周期了! - 简书 (jianshu.com)
Spring源码系列 — Bean生命周期 - 怀瑾握瑜XI - 博客园 (cnblogs.com)
Spring AOP也是在初始化完成后,通过BeanPostProcessor的后置方法运行的。方法内容为,通过切点(如execution(* *.find*(..))
)对bean中的方法进行匹配,如果匹配成功就为这个bean生成代理对象并放入容器中来,之后从容器中获取的都是代理对象。
Spring AOP 源码分析 - 筛选合适的通知器 | 田小波的技术博客 (tianxiaobo.com)
class ApplicationContextAwareProcessor implements BeanPostProcessor {
......
@Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
if (System.getSecurityManager() != null &&
(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}
if (acc != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareInterfaces(bean);
return null;
}
}, acc);
}
else {
invokeAwareInterfaces(bean);
}
return bean;
}
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
}
......
}
销毁阶段,调用DisposableBean接口的destroy方法,若容器关闭,则调用所有bean的destroy方法。如果在xml中自定义了销毁方法(通过destroy-method属性),则调用自定义的方法。
谈一下Spring AOP?
AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。AOP相关概念可以参考:Spring AOP 源码分析系列文章导读。
如何接入
实现元素
TODO(连接点,切点,切面,通知)
生成代理类
Spring启动的时候根据Spring AOP的相关配置(注解或xml配置)生成通知器类(Advisor,可以获取通知),然后通过代理BeanPostProcessor,在bean初始化结束后为每个bean查找合适的通知器,找到了就为bean生成代理对象并放入容器中,之后从容器中获取的就是代理对象。
public interface Advisor {
Advice getAdvice();
...
}
AOP中生成代理类总共有两种方式:JDKProxy和CGLIB。若目标bean实现了接口,就使用JDKProxy,否则使用CGLIB。也可以通过配置制定开启CGLIB代理。如设置下面属性为true。
<aop:aspectj-autoproxy proxy-target-class="true"/>
执行代理方法
AOP生成的代理类,业务增强的逻辑是放到拦截器中的(把before、after等通知封装成拦截器),在执行代理类的方法时先获取这个方法匹配的通知器,然后将通知器中的通知转化成相应的拦截器,最终获得一个ArrayList类型的拦截器列表。(先会去缓存中获取方法的拦截器列表,拿不到再自己组装)
之后按顺序调用拦截器的invoke方法。调用的逻辑是,对于前置通知拦截器,invoke方法是先执行通知逻辑,然后调用下一个拦截器的invoke方法;对于后置通知拦截器,invoke方法先调用下一个拦截器的invoke方法,然后再执行通知逻辑,最后一个拦截器执行目标方法。这就使得目标方法是在前置通知和后置通知之间执行(看下面代码和图片)。
// 前置通知拦截器
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {
......
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
return mi.proceed();
}
......
}
// ReflectiveMethodInvocation.proceed()
@Override
public Object proceed() throws Throwable {
// 如果是最后一个拦截器,执行目标方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 获取下一个拦截器,索引++
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
......// 执行下一个拦截器的invoke()
interceptorOrInterceptionAdvice.invoke(this)
......
}
Spring中实现AOP的几种方式?
Spring AOP的实现发生在bean初始化之后,通过BeanPostProcessor接口类来生成代理类。因此在ApplicationContext构造完成之后,代理类已经被生成,后面从IOC容器获取的都是代理类。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。当类实现了接口,就是用JDKProxy;否则使用cglib。
-
JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
-
如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
具体可以参见:【Java基础】代理模式、静态代理、动态代理、JDK与CGLIB代理区别、在sp
ring AOP中的应用_奋进的小白粥-CSDN博客。
Spring中循环依赖如何解决?说一下解决的原理
将依赖注入的方式改成setter注入即可。
Spring中出现循环依赖问题的场景最常见于使用构造器进行依赖注入的情况,因为使用构造器进行依赖注入时,要求被依赖属性的实例化发生在当前类被实例化之前。假设A和B相互依赖且都是用构造器进行注入,如果要实例化A,必须先创建B,保证容器中有B这个对象,A才能实例化成功;而实例化B的时候,又必须保证A先被创建,所以就会陷入一个死循环,A和B都不能被实例化。而采用setter注入时,依赖注入发生在bean实例化之后,在A和B被创建出来之后再去进行依赖注入,这样就能注入成功。
推荐参考文章:Spring-bean的循环依赖以及解决方式_惜暮-CSDN博客_spring循环依赖