Spring
- Spring的整体架构
- Core Container
Core和Beans模块提供了Spring最基础的功能,提供IOC和依赖注入特性。
Context模块基于Core和Bean来构建,它提供了用一种框架风格地方式来访问对象
Expression Language,表达式语言模块,提供了在运行期间查询和操作对象图的强大能力
- Data Access
JDBC模块,提供对JDBC的抽象,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码。
ORM模块,提供了常用的“对象/关系”映射API的集成层。其中包括JPA、JDO、Hibernate和iBatis.利用ORM封装包,可以混合使用所有Spring提供的特性进行“对象/关系”映射,如简单声明式事务管理。
OXM模块,提供一个支持Object和XML进行映射的抽象层。其中包括JAXB、Castor、XMLBeans、JiBX和XStream。
JMS模块,提供一套“消息生产者、消费者”模板用于更加简单的使用JMS,JMS用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
Transaction模块,支持程序通过简单声明式事务管理,只要是Spring管理对象都能得到Spring管理事务的好处,即使是POJO,也可以为他们提供事务。
- Web
web-socket模块,websocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,spring支持websocket通信。
web模块,提供了基础的web功能。
web-servlet模块,提供了web应用的model-view-controller(MVC)实现。spring mvc框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的他技术协作。
web-portlet模块,提供了在portlet环境下的mvc实现。
Bean的创建过程
refresh流程
refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 ApplicationContext 容器,容器必须调用 refresh 才能正常工作
点击查看【processon】
解决循环依赖
- Spring通过缓存的方法将半成品对象保存在缓存中,以此解决循环依赖问题
- 如果仅以一级缓存解决循环依赖,那么在实现上可以通过在 A 对象 newInstance 创建且未填充属性后,直接放入缓存中。
- 在 A 对象的属性填充 B 对象时,如果缓存中不能获取到 B 对象,则开始创建 B 对象,同样创建完成后,把 B 对象填充到缓存中去。
- 接下来就开始对 B 对象的属性进行填充,恰好这会可以从缓存中拿到半成品的 A 对象,那么这个时候 B 对象的属性就填充完了。
- 最后返回来继续完成 A 对象的属性填充,把实例化后并填充了属性的 B 对象赋值 给 A 对象的 b 属性,这样就完成了一个循环依赖操作。
Spring 中实现了三级缓存用于解决循环依赖问题
- 关于循环依赖在我们目前的 Spring 框架中扩展起来也并不会太复杂,主要就是对于创建对象的提前暴露,如果是工厂对象则会使用 getEarlyBeanReference 逻辑提前将工厂对象存放到三级缓存中。等到后续获取对象的时候实际拿到的是工厂对象中 getObject,这个才是最终的实际对象。
- 在创建对象的 AbstractAutowireCapableBeanFactory#doCreateBean 方法中,提前暴露对象以后,就可以通过接下来的流程,getSingleton 从三个缓存中以此寻找对象,一级、二级如果有则直接取走,如果对象是三级缓存中则会从三级缓存中获取后并删掉工厂对象,把实际对象放到二级缓存中。
- 最后是关于单例的对象的注册操作,这个注册操作就是把真实的实际对象放到一级缓存中,因为此时它已经是一个成品对象了。
一级缓存:存放普通对象
二级缓存:提前暴露对象,存放没有完全实例化好的对象
三级缓存:存放代理对象
事务失效
1. 抛出检查异常导致事务不能正确回滚
@Service
public class Service1 {
@Autowired
private AccountMapper accountMapper;
@Transactional
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
- 原因:Spring 默认只会回滚非检查异常
- 解法:配置 rollbackFor 属性
@Transactional(rollbackFor = Exception.class)
2. 业务方法内自己 try-catch 异常导致事务不能正确回滚
@Service
public class Service2 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
try {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
- 原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
- 解法1:异常原样抛出
- 在 catch 块添加
throw new RuntimeException(e);
- 在 catch 块添加
- 解法2:手动设置 TransactionStatus.setRollbackOnly()
- 在 catch 块添加
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
- 在 catch 块添加
3. aop 切面顺序导致导致事务不能正确回滚
@Service
public class Service3 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
@Aspect
public class MyAspect {
@Around("execution(* transfer(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
LoggerUtils.get().debug("log:{}", pjp.getTarget());
try {
return pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
- 原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常…
- 解法1、2:同情况2 中的解法:1、2
- 解法3:调整切面顺序,在 MyAspect 上添加
@Order(Ordered.LOWEST_PRECEDENCE - 1)
(不推荐)
4. 非 public 方法导致的事务失效
@Service
public class Service4 {
@Autowired
private AccountMapper accountMapper;
@Transactional
void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
}
- 原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的
- 解法1:改为 public 方法
- 解法2:添加 bean 配置如下(不推荐)
@Bean
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(false);
}
5. 父子容器导致的事务失效
package day04.tx.app.service;
// ...
@Service
public class Service5 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
}
控制器类
package day04.tx.app.controller;
// ...
@Controller
public class AccountController {
@Autowired
public Service5 service;
public void transfer(int from, int to, int amount) throws FileNotFoundException {
service.transfer(from, to, amount);
}
}
App 配置类
@Configuration
@ComponentScan("day04.tx.app.service")
@EnableTransactionManagement
// ...
public class AppConfig {
// ... 有事务相关配置
}
Web 配置类
@Configuration
@ComponentScan("day04.tx.app")
// ...
public class WebConfig {
// ... 无事务配置
}
现在配置了父子容器,WebConfig 对应子容器,AppConfig 对应父容器,发现事务依然失效
- 原因:子容器扫描范围过大,把未加事务配置的 service 扫描进来
- 解法1:各扫描各的,不要图简便
- 解法2:不要用父子容器,所有 bean 放在同一容器
6. 调用本类方法导致传播行为失效
@Service
public class Service6 {
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
- 原因:本类方法调用不经过代理,因此无法增强
- 解法1:依赖注入自己(代理)来调用
- 解法2:通过 AopContext 拿到代理对象,来调用
- 解法3:通过 CTW,LTW 实现功能增强
解法1
@Service
public class Service6 {
@Autowired
private Service6 proxy; // 本质上是一种循环依赖
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
System.out.println(proxy.getClass());
proxy.bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
解法2,还需要在 AppConfig 上添加 @EnableAspectJAutoProxy(exposeProxy = true)
@Service
public class Service6 {
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
((Service6) AopContext.currentProxy()).bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
7. @Transactional 没有保证原子行为
@Service
public class Service7 {
private static final Logger logger = LoggerFactory.getLogger(Service7.class);
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
int fromBalance = accountMapper.findBalanceBy(from);
logger.debug("更新前查询余额为: {}", fromBalance);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
public int findBalance(int accountNo) {
return accountMapper.findBalanceBy(accountNo);
}
}
上面的代码实际上是有 bug 的,假设 from 余额为 1000,两个线程都来转账 1000,可能会出现扣减为负数的情况
- 原因:事务的原子性仅涵盖 insert、update、delete、select … for update 语句,select 方法并不阻塞
8. @Transactional 方法导致的 synchronized 失效
针对上面的问题,能否在方法上加 synchronized 锁来解决呢?
@Service
public class Service7 {
private static final Logger logger = LoggerFactory.getLogger(Service7.class);
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public synchronized void transfer(int from, int to, int amount) {
int fromBalance = accountMapper.findBalanceBy(from);
logger.debug("更新前查询余额为: {}", fromBalance);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
public int findBalance(int accountNo) {
return accountMapper.findBalanceBy(accountNo);
}
}
答案是不行,原因如下:
- synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内
- 解法1:synchronized 范围应扩大至代理方法调用
- 解法2:使用 select … for update 替换 select
Spring单例模式线程安全问题
单例模式的Bean是全局共享的,会存在线程安全问题。
如果单例Bean是有状态的(拥有成员变量),那么需要开发人员保证线程安全问题。
解决方法:
1、改变Bean的作用域,从 singleton 更改为 prototype
2、在 ThreadLocal 中存入线程隔离的数据
3、使用 synchronized、CAS 等机制保证共享变量的线程安全
Spring MVC
Spring MVC 基础组件
①用户发送请求至前端控制器DispatcherServlet。
②DispatcherServlet收到请求调用HandlerMapping处理器映射器。
③处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
④DispatcherServlet调用HandlerAdapter处理器适配器。
⑤HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
⑥Controller执行完成返回ModelAndView。
⑦HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
⑧DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
⑨ViewReslover解析后返回具体View。
⑩DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。
Spring MVC 执行流程
- 准备阶段
- 匹配阶段
- 执行阶段
准备阶段
- 在 Web 容器第一次用到 DispatcherServlet 的时候,会创建其对象并执行 init 方法
- init 方法内会创建 Spring Web 容器,并调用容器 refresh 方法
- refresh 过程中会创建并初始化 SpringMVC 中的重要组件, 例如 MultipartResolver,HandlerMapping,HandlerAdapter,HandlerExceptionResolver、ViewResolver 等
- 容器初始化后,会将上一步初始化好的重要组件,赋值给 DispatcherServlet 的成员变量,留待后用
匹配阶段
- 用户发送的请求统一到达前端控制器 DispatcherServlet
- DispatcherServlet 遍历所有 HandlerMapping ,找到与路径匹配的处理器
- 将 HandlerMethod 连同匹配到的拦截器,生成调用链对象 HandlerExecutionChain 返回
- 遍历HandlerAdapter 处理器适配器,找到能处理 HandlerMethod 的适配器对象,开始调用
执行阶段
- 执行拦截器 preHandle
- 由 HandlerAdapter 调用 HandlerMethod
- 执行裹层没有异常:① 返回 ModelAndView② 执行拦截器 postHandle 方法③ 解析视图,得到 View 对象,进行视图渲染
- 出现异常:进入 HandlerExceptionResolver 异常处理流程
- 最后都会执行拦截器的 afterCompletion 方法
自定义拦截器和异常处理器
自定义拦截器
1、编写一个拦截器实现HandlerInterceptor接口
2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
}
}
// 自定义拦截器
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
/**
* 目标方法执行完成以后
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行{}",modelAndView);
}
/**
* 页面渲染以后
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常{}",ex);
}
}
自定义异常处理器
1、创建异常处理器类实现HandlerExceptionResolver
2、配置异常处理器
3、编写异常页面
/*
参数Exception ex:异常对象
返回值 ModelAndView:跳转的视图信息
*/
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
//处理异常的代码实现
if (ex instanceof MyException) {
modelAndView.setViewName("error1");
}
else if (ex instanceof ClassCastException) {
modelAndView.setViewName("error2");
}
return modelAndView;
}
}
// 在xml中配置异常处理器
<bean id="exceptionResolver"
class="com.itheima.exception.MyExceptionResolver"/>
项目中异常处理的方式面试题
- 自定义错误页
Spring底层提供DefaultHandlerExceptionResolver用于处理异常,捕获异常后将HTTP状态码(一般为4xx或者5xx)做为请求路径,访问并展示相关资源。因此可通过自定义404.html页面实现自定义错误页
- error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
@ControllerAdvice
+@ExceptionHandler
处理全局异常(常用)
底层是 ExceptionHandlerExceptionResolver 支持的
@ControllerAdvice // 表明这是一个异常处理类
public class MyExceptionHandler{
// @ExceptionHandler表明可处理异常的类型,可传入一个class数组作为参数
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String handler1(Exception e) {
System.out.println(e);
return "出错了";
}
}
自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
@Component public class MyExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //创建ModelAndView对象 ModelAndView modelAndView = new ModelAndView(); //处理异常的代码实现 if (ex instanceof MyException) { modelAndView.setViewName("error1"); } else if (ex instanceof ClassCastException) { modelAndView.setViewName("error2"); } return modelAndView; } }
自定义异常类,使用
@ResponseStatus
注解进行标注
底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用response.sendError(statusCode, resolvedReason);tomcat发送的/error
// controller
@Controller
public class MyController() {
@RequestMapping("/123")
public String test() {
throw new MyException("test");
}
}
// 自定义异常
// @ResponseStatus可以定义状态码等信息
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "无权限")
public class MyException extends RuntimeException() {
// 提供构造方法
public MyException(String message) {
super(message);
}
}
- 框架提供的defaultHandlerExceptionResolver自动处理未第定义处理的异常
Spring框架在运行时抛出的异常由默认提供的异常解析器进行处理
Spring Boot
Spring Boot 自动装配原理
自动配置原理
@SpringBootConfiguration 是一个组合注解,由 @ComponentScan、@EnableAutoConfiguration 和 @SpringBootConfiguration 组成
- @SpringBootConfiguration 与普通 @Configuration 相比,唯一区别是前者要求整个 app 中只出现一次
- @ComponentScan
- excludeFilters - 用来在组件扫描时进行排除,也会排除自动配置类
- @EnableAutoConfiguration 也是一个组合注解,由下面注解组成
- @AutoConfigurationPackage – 用来记住扫描的起始包
- @Import(AutoConfigurationImportSelector.class) 用来加载
META-INF/spring.factories
中的自动配置类
为什么不使用 @Import 直接引入自动配置类
有两个原因:
- 让主配置类和自动配置类变成了强耦合,主配置类不应该知道有哪些从属配置
- 直接用
@Import(自动配置类.class)
,引入的配置解析优先级较高,自动配置类的解析应该在主配置没提供时作为默认配置
因此,采用了 @Import(AutoConfigurationImportSelector.class)
- 由
AutoConfigurationImportSelector.class
去读取META-INF/spring.factories
中的自动配置类,实现了弱耦合。 - 另外
AutoConfigurationImportSelector.class
实现了 DeferredImportSelector 接口,让自动配置的解析晚于主配置的解析