spring mvc流程?
答 在整个 Spring MVC 框架中, DispatcherServlet 处于核心位置,负责协调和组织不同组件以完成对请求处理并返回响应的工作
SpringMVC 处理请求过程:
- 当一个请求来时,首先会被DispatcherServlet拦截,然后根据请求信息查找HandlerMapping,返回一个执行链
- 依次执行,到达controller,执行业务逻辑,返回ModelAndView给DispatcherServlet;
- DispatcherServlet在请求进行视图解析,然后然会View
最后进行视图渲染
![spring mvc执行流程](https://i-blog.csdnimg.cn/blog_migrate/9030f496d56814e66c0c8a1f1e331706.png#pic_center)
spring AOP实现原理?
答 AOP:面向切面的编程.是对传统 OOP(面向对象编程) 的补充.AOP 的主要对象是切面(aspect),Filter(过滤器)也是一种 AOP.比如事务和日志等也会用AOP实现。
实现原理是java动态代理机制和CGLIB动态代理。
- JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着Proxy利用 InvocationHandler动态创建一个符合某一接口的实例, 生成目标类的代理对象。
- 如果代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个字节码层面的代码生成类库,可以在运行时为指定类动态的生成一个子类对象,并覆盖其中特定方法,从而实现AOP。CGLIB是通过继承的方式做动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
spring IOC?
答 ioc:控制反转,将bean的创建交由spring管理,由spring维护bean的生命周期.
两种实现:
- 实现 BeanFactory接口的简单容器(包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系)
- ApplicationContext应用上下文,作为容器的高级形态存在( 继承MessageSource,因此支持国际化; 统一的资源文件访问方式; 提供在监听器中注册bean的事件; 同时加载多个配置文件; 载入多个(有继承关系)上下文
@Autowired与@Resource有什么区别??
答
- @Autowired按byType自动注入,而@Resource默认按 byName自动注入;
- 当不止一个实例时,@Autowired需要配合@Qualifier来定位需要装载的实例;
- Autowired与Resource的后置处理器类不一样
spring IOC 的初始化过程?
答
- 第一个过程是:BeanDefinition的Resource定位
- 第二个过程是:BeanDefinition的载入和解析
- 第三个过程是:BeanDefinition在IoC容器中的注册
IOC容器的初始化过程-refresh()?
答
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
// 这里是子类中启动FreshBeanFactory的地方
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
// 设置BeanFactory的后置处理
this.postProcessBeanFactory(beanFactory);
// 调用BeanFactory的后置处理,这些处理器是在Bean定义中向容器注册的,
// 当这个方法执行完成,所有的Bean的配置信息就会被放到BeanDefinitionMap中
this.invokeBeanFactoryPostProcessors(beanFactory);
// 注册后置处理器,在bean创建的时候调用
this.registerBeanPostProcessors(beanFactory);
// 对消息源进行初始化
this.initMessageSource();
// 初始化上下文中的事件机制
this.initApplicationEventMulticaster();
// 初始化其他特殊Bean
this.onRefresh();
// 检查监听器并向容器注册
this.registerListeners();
// 初始化所有非懒加载的bean,并完成了依赖注入
this.finishBeanFactoryInitialization(beanFactory);
// 发布容器事件,结束refresh过程
this.finishRefresh();
} catch (BeansException var9) {
// 防止资源占用,销毁之前创建的Bean(finishBeanFactoryInitialization创建的)
this.destroyBeans();
// 重置‘active’标志
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
那IOC有什么优缺点吗?
答
- 优点:很明显,实现了组件之间的解耦,提高程序的灵活性和可维护性。
- 缺点:对象生成是反射实现,在效率上有些损耗。但相对于IOC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高
Spring Bean 的生命周期?
答
- 通过构造器或工厂方法创建 Bean 实例
- 为 Bean 的属性设置值和对其他 Bean 的引用
- Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean
- 将 Bean 实 例 传 递 给 Bean 后 置 处 理 器 的postProcessBeforeInitialization 方法
- 调用 Bean 的初始化方法(init-method)
- 将 Bean 实 例 传 递 给 Bean 后 置 处 理 器 的postProcessAfterInitialization 方法
- Bean 可以使用了
- 当容器关闭时, 调用 Bean 的销毁方法(destroy-method)
BeanFactory 和 ApplicationContext 有什么区别?
答
- BeanFactory可以理解为含有 bean 集合的工厂类。BeanFactory 包含了各种 bean 的定义,以便在接收到客户端请求时将对应的 bean 实例化(懒加载)。 BeanFactory 还能在实例化对象时生成协作类之间的关系。此举将 bean 自身与bean 客户端的配置中解放出来。BeanFactory 还包含 了 bean 生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。
- applicationcontext 拥有beanfactory的所有功能。applicationcontext 在此基础上还提供了其他的功能。提供了支持国际化、统一的资源文件读取方式、已在监听器中注册的 bean 的事件等
Spring Bean 的作用域之间有什么区别?
答
- singleton:这种 bean 范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个 bean 的实例,单例的模式由bean factory 自身来维护
- prototype:原形范围与单例范围相反,为每一个 bean 请求提供一个实例。
- request:在请求 bean 范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
- Session:与请求范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后,bean
会随之失效。 - global- session:全局变量。
Spring 框架中的单例 Bean是线程安全的么?
答
Spring 框架并没有对单例 bean 进行任何多线程的封装处理。
关于单例 bean的线程安全和并发问题需要开发者自行去处理。但实际上,大部分的 Spring bean 并没有可变的状态(比如 Servies类 和 DAO类),所以在某种程度上说 Spring 的单例 bean 是线程安全的。
如果你的 bean 有多种状态 的话(比如 View Model对象),就需要自行保证线程安全。 最浅显的解决办法就是将多态 bean 的作用域由“singleton”变更为“prototype”,还有使用ThreadLocal来处理一些值
请解释 Spring Bean 的自动装配?
答
在 Spring 框架中共有 5 种自动装配
- no:这是 Spring 框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在 bean 定义中用标签明确的设置依赖关系。
- byName:该选项可以根据 bean 名称设置依赖关系。当向一个 bean 中自动装配一个属性时,容器将根据 bean 的名称自动在在配置文件中查询一个匹配的 bean。如果找到的话,就装配这个属性,
如果没找到的话就报错。 - byType:该选项可以根据 bean 类型设置依赖关系。当向一个 bean 中自动装配一个属性时,容器
将根据 bean 的类型自动在在配置文件中查询一个匹配的 bean。如果找到的话,就装配这个属性,
如果没找到的话就报错。 - constructor:构造器的自动装配和 byType 模式类似,但是仅仅适用于与有构造器相同参数的 bean,如果在容器中没有找到与构造器参数类型一致的 bean,那么将会抛出异常。
- autodetect:该模式自动探测使用构造器自动装配或者 byType 自动装配。首先,首先会尝试找合
适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在 bean 内部没有找到相应的构造
器或者是无参构造器,容器就会自动选择 byTpe 的自动装配方式。
Spring 框架中有哪些不同类型的事件?
答
Spring 提供了以下 5 中标准的事件:
- 上下文更新事件(ContextRefreshedEvent):该事件会在 ApplicationContext 被初始化或者更 新时发布。也可以在调用 ConfigurableApplicationContext 接口中的 refresh()方法时被触发。
- 上下文开始事件(ContextStartedEvent):当容器调用 ConfigurableApplicationContext 的 Start()方法开始/重新开始容器时触发该事件。
- 上下文停止事件(ContextStoppedEvent):当容器调用 ConfigurableApplicationContext 的 Stop()方法停止容器时触发该事件。
- 上下文关闭事件(ContextClosedEvent):当 ApplicationContext 被关闭时触发该事件。容器被 关闭时,其管理的所有单例 Bean 都被销毁。
- 请求处理事件(RequestHandledEvent):在 Web 应用中,当一个 http 请求(request)结束触 发该事件。
Spring 框架中都用到了哪些设计模式?
答
Spring 框架中使用到了大量的设计模式:
- 代理模式—在 AOP 和 remoting 中被用的比较多。
- 单例模式—在 spring 配置文件中定义的 bean 默认为单例模式。
- 模板方法—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
- 工厂模式—BeanFactory 用来创建对象的实例
- 委派模式:Spring提供了DispatcherServlet来对请求进行分发
spring的事务传播行为?
答
- PROPAGATION_REQUIRED 如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
- PROPAGATION_SUPPORTS 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
- PROPAGATION_MANDATORY 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW 创建新事务,无论当前存不存在事务,都创建新事务
- PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
spring 循环依赖和如何处理?
答
循环依赖
Spring在进行bean的创建时,如果有两个类,A需要B,B又需要A,此时就会出现循环依赖,循环依赖只有单例模式才支持。
解决方式
-
我们向BeanFactory获取A类型的bean,第一次没有于是创建对象。
-
接着完成两件事
- 将自己的工厂放到二级缓存
- 将beanName放入正在创建的集合中
-
A类型bean进行属性注入(populdateBean),触发了依赖注入B,这时b也不存在,于是也开始构建b,重复步骤1
-
接着B进行构造(populdateBean),这是需要a,于是触发getSingleton(String beanName, boolean allowEarlyReference)
-
这时候就可以在二级缓存中获取到a的工厂,从而得到a,再将a放入三级缓存中,
-
b得到的a,于是成功初始化,接着a也就能得到b,初始化成功
获取对象的源码如下
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
...
// 将自己的工厂放到二级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))
...
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
...
// 将Factory放入二级缓存
this.singletonFactories.put(beanName, singletonFactory);
...
}
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
...
// 将该beanName放入正在创建的集合列表
beforceSingletonCreation(beanName)
...
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//从一级缓存中获取对象
Object singletonObject = this.singletonObjects.get(beanName);
// 如果没有获取到,则判断是否正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//从三级缓存中获取尚未完成初始化的对象
singletonObject = this.earlySingletonObjects.get(beanName);
//如果不存在,且允许循环依赖
if (singletonObject == null && allowEarlyReference) {
// 就从二级缓存中获取对象的工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//根据工厂得到实例,然后将他放到三级缓存中:
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
spring 为什么不一开始直接将对象放到三级缓存,还要有二级缓存?
答
由于spring还需要对未完成的实例做封装,比如AOP代理等,如果一开始将未完成的实例放到三级缓存,则无法提前完成aop代理,则会在使用中可能出现其他问题。
spring 三级缓存?
答
singletonFactories : 单例对象工厂的缓存
earlySingletonObjects :缓存未初始化完成但已经被spring代理的对象
singletonObjects:缓存完成的单例对象
spring注入方式?
答
- Set注入
这是最简单的注入方式,假设有一个SpringAction,类中需要实例化一个SpringDao对象,那么就可以定义一个private的SpringDao成员变量,然后创建SpringDao的set方法(这是ioc的注入入口) - 构造器注入
这种方式的注入是指带有参数的构造函数注入,看下面的例子,我创建了两个成员变量SpringDao和User,但是并未设置对象的set方法,所以就不能支持第一种注入方式,这里的注入方式是在SpringAction的构造函数中注入,也就是说在创建SpringAction对象时要将SpringDao和User两个参数值传进来 - 静态工厂的方法注入
静态工厂顾名思义,就是通过调用静态工厂的方法来获取自己需要的对象,为了让spring管理所有对象,我们不能直接通过"工程类.静态方法()"来获取对象,而是依然通过spring注入的形式获取 - 实例工厂的方法注入
实例工厂的意思是获取对象实例的方法不是静态的,所以你需要首先new工厂类,再调用普通的实例方法
spring通知?
答
- 前置通知:
- 后置通知:
- 最终通知:
- 环绕通知:
- 异常通知:
Spring如何处理线程并发问题?
答
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
Spring解决跨域?
答
- 通过注解的方式允许跨域(spring 4.2)
我们可以在Controller类或其方法上加@CrossOrigin注解,来使之支持跨域。
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/User")
public class UserController {
}
//其中origins为CrossOrigin的默认参数,即跨域来源,*即任何来源,也可以是其他域名。即可以以以下形式:
@CrossOrigin("http://test.com") @CrossOrigin(origins="http://test.com",maxAge=3600)
- 用filter过滤器,重写请求头
@Component
public class CorsFilter implements Filter {
final static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CorsFilter.class);
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
System.out.println("*********************************过滤器被使用**************************");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
spring boot 自动装配原理?
答
自动装配的实现是基于@Conditional(条件化配置)实现的
@Conditional:Spring的条件化配置允许配置存在于应用中,但是若未满足某些特定条件,则会忽略这些配置。
在spring boot项目中,会存在一个名为spring-boot-autoconfigure的jar包,条件化配置就是在这个jar里面实现的,它用到了如下的条件化注解,这些注解都是以@ConditionalOn开头的,他们都是应用了@Conditional的组合注解:
步骤:
-
spring boot项目的启动类用的注解 @SpringBootApplication 是一个组合注解,其中@EnableAutoConfiguration是自动配置相关的;
-
而这个@EnableAutoConfiguration注解里面有个@Import注解导入了 AutoConfigurationImportSelector 用来实现具体的功能
-
进入父类,里面有个方法 selectImports() 调用了方法getCandidateConfigurations(),进而调用了SpringFactoriesLoader.loadFactoryNames()方法
-
SpringFactoriesLoader.loadFactoryNames方法会扫描具有META-INF/spring.factories文件的jar包,而我们的spring-boot-autoconfigure.jar里面就有一个这样的文件,此文件中声明了具体有哪些自动配置:
这样就实现了自动装配
spring 转换器?
答
Spring定义了3中类型的转换器接口:
- Converter<S,T>接口:提供单一方法:T convert(S source);该方法负责将S类型的对象转换为T类型的对象;
ConverterFactory<S,R> :提供单一方法 Converter<S,T> getConverter(Class targetType);该方法负责将一种类型的对象转换为另一种类型及其子类对象,例如String转换为Number及Number子类Integer、Double等对象; - GenericConverter接口:该接口会根据源类对象及目标类对象的上下文信息进行类型转换。
接口定义的方法如下:- Set<GenericConverter.ConvertiblePair> getConvertibleTypes();
- Object convent(Object source ,TypeDescriptor sourceType,TypeDescriptor targetType);
Spring boot 启动流程?
答
- 创建SpringBootApplication对象
1)保存主配置类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
2)判断当前是否为一个WEB应用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
3)查找类路径下META-INF/spring.factories中配置的ApplicationContextInitializer
setInitializers() --> SpringFactoriesLoader.loadFactoryNames(type, classLoader)
4) 在查找ApplicationListener
setListeners() --> SpringFactoriesLoader.loadFactoryNames(type, classLoader)
5)从多个配置类中找到有main方法的主配置类
this.mainApplicationClass = deduceMainApplicationClass();
- 启动应用 run
1) 获取SpringApplicationRunListener也是从META-INF/spring.factories中
SpringApplicationRunListeners listeners = getRunListeners(args);
2)启动SpringApplicationRunListeners
listeners.starting();
3)准备环境:创建环境并回调listeners.environmentPrepared(environment);
为环境准备完成
prepareEnvironment(listeners, applicationArguments);
4)创建ApplicationContext,会根据是否是WEB环境创建Web IOC或者普通IOC
createApplicationContext();
5) 准备上下文环境:
prepareContext()
这个方法中会执行
①保存environment到IOC;
②调用 applyInitializers(context);回调之前从配置中读取的ApplicationContextInitializer的Initialize方法
③调用listeners.contextPrepared(context);;回调listeners.contextPrepared
④调用listeners.contextLoaded(context); 同理
6)IO容器初始化
refreshContext(context);
7)调用listeners的started方法
listeners.started(context);
8)从IOC容器中获取ApplicationRunner和CommandLineRunner进行回调;ApplicationRunner先回调,CommandLineRunner后回调
callRunners(context, applicationArguments);
9) 调用listeners.running方法