一、Spring
1. Spring 概述
Spring框架是一个开放源代码的J2EE应用程序框架,是针对bean的生命周期进行管理的轻量级容器。 提供了功能强大IOC、AOP及Web MVC等功能。
2. Spring Ioc 容器构建流程
问:如何实现IoC构建完成之后执行一些逻辑?
答:实现ApplicationListener接口,监听 ContextRefreshedEvent事件。
3. Spring Bean 的生命周期
大概流程就是:
- bean实例化,创建一个bean对象。
- 属性填充阶段.
- 初始化阶段。
- bean的正常使用阶段
- 销毁阶段.
4. BeanFactory
- IoC容器,提供完整的IoC服务支持。
- 也是最基础的接口,本质是个工厂类,用于管理Bean的工厂。
- 核心功能是加载 bean, 也就是 getBean 方法。
- 通常我们不会直接使用该接口,而是使用其子接口ApplicationContext.
ApplicationContext:高级IoC容器,BeanFactory的子接口,在它基础上进行扩展,包含它的所有功能,还提供了其他高级特性,例如:事件发布、国际化信息支持、统一资源加载策略等。正常情况下,我们使用的都是ApplicationContext。
5. Spring AOP 实现原理
本质是通过动态代理实现的,步骤如下:
- 获取增强器,例如被@Aspect注解修饰的类。
- 在创建bean时,检查是否有增强器作用于这个bean(也即bean是否在增强器的execution中),如果是,则将增强器作为拦截器参数,使用动态代理创建bean的代理对象实例。
- 当我们调用被增强过的bean时,就会走到代理类中,从而触发增强器,本质跟拦截器类似。
5.1 多个AOP的顺序如何指定
通过ordered和priorityOrdered接口进行排序。priorityOrdered 接口优先级比Ordered 接口高。
如果同时实现ordered和priorityOrdered 则再按照order 值排序,值越小,优先级越高。
5.2 AOP创建代理的方式
- JDK动态代理 和 cglib 代理。
- 通常来说:如果被代理对象实现了接口,则用JDK动态代理,否则用 cglib 代理。
- 也可以通过指定proxyTargetClass=true 来实现强制走 cglib 代理。
问:为什么JDK 动态代理只能对实现了接口的类生成代理?
答:因为通过JDK 动态代理生成的类已经继承了Proxy,所以无法再使用继承的方式对类实现代理。
5.3 JDK 动态代理和 cglib 代理的区别
- JDK 动态代理本质上是实现了被代理对象的接口,而 cglib 本质上是继承了被代理对象,覆盖其中的方法。
- JDK 动态代理只能对实现了接口的类生成代理,cglib 则没有这个限制。但是 cglib 因为使用继承实现,所以 Cglib 无法代理被final修饰的方法和类。
- 在调用代理方法上,JDK是通过反射机制调用,Cglib 是通过FastClass 直接调用。FastClass 就是使用 index 作为入参,可以直接定位到要调用的方法直接进行调用。
- 在性能上,JDK1.7之前,由于使用了FastClass 机制,Cglib在执行效率上比JDK动态代理快,但随着JDK动态代理的不断优化,从JDK1.7开始,JDK动态代理的性能明显比Cglib 更快了。
6.Spring 事务
6.1 Spring 事物传播行为
含义:父方法 调用 子方法,事务如何传播?一共有7种:
- require:默认传播级别,父方法有事务就加进去,没有事物就新建事务执行。
- require_new:父方法无论有没有事务,都以新事务执行。
- supports:父方法有事务则加进去,没有事务就以非事务执行。
- not_supports:挂起父方法事务,以非事务执行。执行完之后恢复父方法事务。
- Mandatory:父方法必须存在事务,否则抛出异常。
- never:父方法不能存在事务,否则抛出异常。
- nested:嵌套事务。
6.2 Spring 事物隔离级别
Spring 事务隔离级别底层是基于数据库的,Spring 本身没有自己的一套隔离级别。
- DEFAULT:使用数据库默认的隔离级别。
- READ_UNCOMMIT:读未提交,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读)。
- READ_COMMIT:读已提交,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复度),SQL server的默认级别。
- REPEATABLE-READ:可重复读,一个事务提交后,并且另一个事务提交之后才可以读到上一个事务的值。mysql的默认隔离级别(mysql通过加锁解决幻读)。
- SERIALIZABLE:串行化,写会加写锁(X锁),读会加读锁(S锁),无事务并发问题。
问:如何实现 Spring 事务隔离级别和数据库不一致?
答:Spring 事务隔离级别本质上还是通过数据库控制的,具体是在执行事务前先执行命令修改数据库的隔离级别,命令格式如下:
set session transaction isolation level read uncommit;
6.3 Spring 事务实现原理
底层实现的主要技术:AOP(动态代理)+ ThreadLocal + try/catch。
- 动态代理:基本所有要进行逻辑增强的地方都会用到动态代理,AOP底层是通过动态代理实现的。
- ThreadLocal:用于线程间的资源隔离,以此实现不同线程可以使用不同的数据源、隔离级别等。
- try/catch:最终是执行 commit 还是 rollback,通过是否抛出异常来决定。
核心伪代码:
public void invokeWithTransaction() {
// 1.事务资源准备
try {
// 2.业务逻辑处理,也就是调用被代理的方法
} catch (Exception e) {
// 3.抛出异常,进行回滚并将异常抛出
} finally {
// 4.正常执行,进行事物的提交
// 返回业务逻辑处理结果
}
}
详细流程图如下:
6.4. Spring 事物失效的场景
- 被 final 或者 static 修饰的方法;动态代理无法重写该方法。
- 抛出的异常不是Spring事物支持的异常,例如:Exception,可以指定RollbackFor=Exception.class
- 指定了RollbackFor异常,但是方法没有抛这个类型的异常。
- 数据库本身不支持事物。
- 注解所在的类没有被Spring管理。
- 异常被捕获了,没有重新抛出异常。
- 方法自身(this)调用问题。
- 事务传播类型不支持事务导致事物失效。
- 方法里面开多线程也会导致事务失效。
7. Spring 循环依赖问题
循环依赖就是循环引用,比如bean A需要引用bean B,bean B需要引用bean A,形成循环关系;
Spring 通过提前暴露 bean 的引用来解决循环依赖的。具体如下:
- 通过构造函数创建不完整的bean(未进行属性填充和初始化);
- 提前曝光bean 实例的 ObjectFactory(将ObjectFactory 放到 singletonFactories 缓存)
- 如果出现循环引用,则通过缓存中的 ObjectFactory 拿到 bean 实例,从而避免死循环。
举个例子:A 依赖了 B,B也依赖了 A,那么注入的过程如下。
- 检查缓存中A是否存在,不存在则进行实例化。
- 通过构造函数创建 bean A,并通过 ObjectFactory 提前曝光 bean A。
- A走到属性填充的时候,发现依赖B,则开始实例化B。
- 检查B是否在缓存中,不存在则进行实例化。
- 通过构造函数创建 beanB,并通过 ObjectFactory 提前曝光 bean B。
- B走到属性填充的时候,发现依赖A,则开始实例化A。
- 检查A是否在缓存中,存在则通过 ObjectFactory 拿到 bean A,并返回。
- B创建完毕之后,返回A的创建流程,A走完接下来的流程,直至创建完毕。
通过 ObjectFactory 拿到的 bean 实例是不完整的,但因为是单例,所以它的地址不会变化,后续创建完成后,拿到的还是完成的 bean 实例。
无法解决循环依赖的情况:
- 构造函数注入bean的情况。
- 多实例通过setter注入。
- 单例的代理bean通过setter注入。
- 设置@DependsOn注解的bean。
7.1 Spring 三级缓存
解决循环依赖用到的三个缓存。
- 一级缓存:singletonObjects,存放初始化后的单例对象;
- 二级缓存:earlySingletonObjects,存放实例化,未完成初始化的单例对象(未完成属性注入的对象);
- 三级缓存:singletonFactories,存放ObjectFactory对象;
三级缓存之间逐级取,流程如下:
- getBean()获取实例,Spring首先从一级缓存singletonObjects中获取;
- 如果获取不到,就从二级缓存earlySingletonObjects中获取,如果还是获取不到意味着bean没有实例化;
- 这时singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取;(代理也是从三级缓存生产的)
- 如果从三级缓存中获取到就从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存;
- 这个bean存在会等待下一次轮寻的时候去赋值(解析@Autowared,@Resource)注解等,属性赋值完成后,将bean存入一级缓存;
7.2@Autowared和@Resource的区别
- @Autowared 默认按类型装配,要求依赖对象必须存在,可以通过require=false设置允许null值。
- @Resource 如果指定了name 和 type,则按照指定的装配;如果不指定,则优先按照名称装配,当找不到与名称匹配的时候才按照类型装配。
二、SpringBoot
1. SpringBoot 概述
Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。
2. SpringBoot 优点
- 快速创建独立运行的spring项目与主流框架集成。
- 使用嵌入式的servlet容器,应用无需打包成war包。
- starters自动依赖与版本控制。
- 大量的自动配置,简化开发,也可修改默认值。
- 准生产环境的运行应用监控。
- 与云计算的天然集成。
3. SpringBoot 核心配置文件
Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。
- application 配置文件主要用于 Spring Boot 项目的自动化配置。
- bootstrap 配置文件有以下几个应用场景。
- 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
- 一些固定的不能被覆盖的属性;
- 一些加密/解密的场景
3.1 配置文件的格式类型
Springboot 配置文件主要有.properties 和 .yml两种格式,区别主要是格式不同。
- .properties
spring.application.name = demo
- .yml
spring:
application:
name: demo
另外.yml不支持@propertySource注解导入配置
4. 核心注解@SpringBootApplication
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解 主要组合包含了以下 3 个注解:
- @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
- @ComponentScan:Spring组件扫描。
5. SpirngBoot 自动装配
通过注解或者一些简单的配置就能在spring boot的帮助下实现某款功能。
5.1 自动装配原理
- 判断自动装配开关是否打开。默认开启。可在 application.properties 或 application.yml 中设置。
spring.boot.enableautoconfiguration=true
- 获取@EnableAutoConfiguration注解中的 exclude 和 excludeName。
- 获取需要自动装配的所有配置类,读取jar包下META-INF/spring.factories文件。
- 通过@OnConditionOnXXX自动加载配置类。
总结:Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖。
5.2 如何实现一个starter
- 创建一个starter项目,Spring 官方定义的 starter 通常命名遵循的格式为 spring-boot-starter-{name}。
- 创建一个ConfigurationProperties用于保存你的配置信息。
- 创建一个AutoConfiguration,引用定义好的配置信息,在AutoConfiguration中实现所有starter应该完成的操作。
- 把AutoConfiguration这个类加入spring.factories配置文件中进行声明。
- 打包项目,之后在一个SpringBoot项目中引入该项目依赖。
5.2.1 创建properties属性类,用于读取属性
@ConfigurationProperties(prefix = "com.test")
public class TestServiceProperties {
private String name = "test";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@ConfigurationProperties配置此注解可以自动导入application.properties配置文件中的属性,前提需要指定属性前缀prefix。如果application.properties文件中未指定相应属性,便使用默认的,如上name=“test”。
5.2.2 创建配置类
public class TestServiceConfiguration {
private String name;
public String getName() {
return "name is " + name;
}
public void setName(String name) {
this.name = name;
}
}
5.2.3 创建自动配置类
@Configuration
@EnableConfigurationProperties(TestServiceProperties.class)
@ConditionalOnClass(TestServiceConfiguration.class)
@ConditionalOnProperty(prefix = "com.test", value = "enabled", matchIfMissing = true)
public class TestServiceAutoConfiguration {
@Autowired
private TestServiceProperties testServiceProperties;
@Bean
@ConditionalOnMissingBean(TestServiceConfiguration.class)
public TestServiceConfiguration testServiceConfiguration() {
TestServiceConfiguration testService = new TestServiceConfiguration();
TestService.setName(testServiceProperties.getName());
return testService;
}
}
- @Configuration:表明此类是一个配置类,将变为一个bean被spring进行管理。
- @EnableConfigurationProperties:启用属性配置,将读取TestServiceProperties里面的属性。
- @ConditionalOnClass:当类路径下面有TestServiceConfiguration此类时,自动配置。
- @ConditionalOnProperty:判断指定的属性是否具备指定的值。
- @ConditionalOnMissingBean:当容器中没有指定bean时,创建此bean。
5.2.4 配置类注册
在resources文件夹下面新建一个META-INF文件,并在下面创建spring.factories文件,将配置类进行注册。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.test.TestServiceAutoConfiguration
5.2.5 使用
自定义的starter编写完毕,执行mvn clean install将项目打成一个jar包。新建一个springboot项目,在pom文件中添加刚刚打包的jar。
在application.properties添加
com.test.name=test
6 SpringBoot 内置Tomcat调优
在 Spring Boot 框架中,我们使用最多的是Tomcat,这是 Spring Boot 默认的容器技术,而且是内嵌式的 Tomcat。Tomcat 是 Apache 基金下的一个轻量级的Servlet 容 器 , 支 持 Servlet 和 JSP 。Tomcat服务器本身具有Web服务器的功能,可以作为独立的Web服务器来使用。
SpringBoot 中 Tomcat 的配置如下(部署在4核8G的服务器中):
最大工作线程数,默认200。
server.tomcat.max-threads=1000
最大连接数默认是10000
server.tomcat.max-connections=10000
等待队列长度,默认100。
server.tomcat.accept-count=1000
最小工作空闲线程数,默认10。
server.tomcat.min-spare-threads=100
-
线程数的经验值为:1核2G内存,线程数经验值200;4核8G内存, 线程数经验值800。(4核8G内存单进程调度线程数800-1000,超过这个并发数之后,将会花费巨大的时间在CPU调度上)。
maxThreads 规定的是最大的线程数目,并不是实际running的CPU数量;实际上,maxThreads的大小比CPU核心数量要大得多。这是因为,处理请求的线程真正用于计算的时间可能很少,大多数时间可能在阻塞,如等待数据库返回数据、等待硬盘读写数据等。因此,在某一时刻,只有少数的线程真正的在使用物理CPU,大多数线程都在等待;因此线程数远大于物理核心数才是合理的。也就是说,Tomcat通过使用比CPU核心数量多得多的线程数,可以使CPU忙碌起来,大大提高CPU的利用率。 -
maxConnections 是Tomcat一瞬间最多能够处理的并发连接数。并发量指的是连接数。
-
acceptCount:队列做缓冲池用,但也不能无限长,消耗内存,出入队列也耗CPU。当连接数达到最大值maxConnections后,系统会继续接收连接,进行排队,但不会超过acceptCount的值。故 tomcat支持的最大连接数 = maxConnections + acceptCount。
Tomcat 有两种处理连接的模式:
- 一种是BIO,一个线程只处理一个Socket连接;
- 另一种就是NIO,一个线程处理多个Socket连接。
由于HTTP请求不会太耗时,而且多个连接一般不会同时来消息,所以一个线程处理多个连接没有太大问题。
问:为什么不开更多线程?
答:多开线程的代价就是增加上下文切换的时间,浪费CPU时间。另外还有就是线程数增多,每个线程分配到的时间片就变少。多开线程并不等于提高处理效率。
问:那增加最大连接数(maxConnections)呢?
答:增加最大连接数,支持的并发量确实可以上去。但是在没有改变硬件条件的情况下,这种并发量的提升必定以牺牲响应时间为代价。
三、SpringMVC
1. SpringMVC 概述
SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于Spring 后续产品,已经融合在 Spring Web Flow 中。
2. SpringMVC 工作流程
- 用户通过浏览器发起 HttpRequest 请求到前端控制器 (DispatcherServlet)。
- DispatcherServlet 将用户请求发送给处理器映射器 (HandlerMapping)。
- 处理器映射器 (HandlerMapping)会根据请求,找到负责处理该请求的处理器,并将其封装为处理器执行链 返回 (HandlerExecutionChain) 给 DispatcherServlet
- DispatcherServlet 会根据 处理器执行链 中的处理器,找到能够执行该处理器的处理器适配器(HandlerAdaptor) --注,处理器适配器有多个
- 处理器适配器 (HandlerAdaptoer) 会调用对应的具体的 Controller
- Controller 将处理结果及要跳转的视图封装到一个对象 ModelAndView 中并将其返回给处理器适配器 (HandlerAdaptor)
- HandlerAdaptor 直接将 ModelAndView 交给 DispatcherServlet ,至此,业务处理完毕
- 业务处理完毕后,我们需要将处理结果展示给用户。于是DisptcherServlet 调用 ViewResolver,将 ModelAndView 中的视图名称封装为视图对象
- ViewResolver 将封装好的视图 (View) 对象返回给 DIspatcherServlet
- DispatcherServlet 调用视图对象,让其自己 (View) 进行渲染(将模型数据填充至视图中),形成响应对象 (HttpResponse)
- 前端控制器 (DispatcherServlet) 响应 (HttpResponse) 给浏览器,展示在页面上。
3. SpringMCV组件说明
- DispatcherServlet:前端控制器,也称为中央控制器,它是整个请求响应的控制中心,组件的调用由它统一调度。
- HandlerMapping:处理器映射器,它根据用户访问的 URL 映射到对应的后端处理器 Handler。也就是说它知道处理用户请求的后端处理器,但是它并不执行后端处理器,而是将处理器告诉给中央处理器。
- HandlerAdapter:处理器适配器,它调用后端处理器中的方法,返回逻辑视图 ModelAndView 对象。
- ViewResolver:视图解析器,将 ModelAndView 逻辑视图解析为具体的视图(如 JSP)。