Spring

  • Spring的整体架构

Spring - 图1

  • 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的创建过程

点击查看【processon】

refresh流程

refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 ApplicationContext 容器,容器必须调用 refresh 才能正常工作
点击查看【processon】

解决循环依赖

  • Spring通过缓存的方法将半成品对象保存在缓存中,以此解决循环依赖问题

image.png

  • 如果仅以一级缓存解决循环依赖,那么在实现上可以通过在 A 对象 newInstance 创建且未填充属性后,直接放入缓存中。
  • 在 A 对象的属性填充 B 对象时,如果缓存中不能获取到 B 对象,则开始创建 B 对象,同样创建完成后,把 B 对象填充到缓存中去。
  • 接下来就开始对 B 对象的属性进行填充,恰好这会可以从缓存中拿到半成品的 A 对象,那么这个时候 B 对象的属性就填充完了。
  • 最后返回来继续完成 A 对象的属性填充,把实例化后并填充了属性的 B 对象赋值 给 A 对象的 b 属性,这样就完成了一个循环依赖操作。

Spring 中实现了三级缓存用于解决循环依赖问题

  • 关于循环依赖在我们目前的 Spring 框架中扩展起来也并不会太复杂,主要就是对于创建对象的提前暴露,如果是工厂对象则会使用 getEarlyBeanReference 逻辑提前将工厂对象存放到三级缓存中。等到后续获取对象的时候实际拿到的是工厂对象中 getObject,这个才是最终的实际对象。
  • 在创建对象的 AbstractAutowireCapableBeanFactory#doCreateBean 方法中,提前暴露对象以后,就可以通过接下来的流程,getSingleton 从三个缓存中以此寻找对象,一级、二级如果有则直接取走,如果对象是三级缓存中则会从三级缓存中获取后并删掉工厂对象,把实际对象放到二级缓存中。
  • 最后是关于单例的对象的注册操作,这个注册操作就是把真实的实际对象放到一级缓存中,因为此时它已经是一个成品对象了。

一级缓存:存放普通对象
二级缓存:提前暴露对象,存放没有完全实例化好的对象
三级缓存:存放代理对象
image.png

事务失效

1. 抛出检查异常导致事务不能正确回滚

  1. @Service
  2. public class Service1 {
  3. @Autowired
  4. private AccountMapper accountMapper;
  5. @Transactional
  6. public void transfer(int from, int to, int amount) throws FileNotFoundException {
  7. int fromBalance = accountMapper.findBalanceBy(from);
  8. if (fromBalance - amount >= 0) {
  9. accountMapper.update(from, -1 * amount);
  10. new FileInputStream("aaa");
  11. accountMapper.update(to, amount);
  12. }
  13. }
  14. }
  • 原因:Spring 默认只会回滚非检查异常
  • 解法:配置 rollbackFor 属性
    • @Transactional(rollbackFor = Exception.class)

2. 业务方法内自己 try-catch 异常导致事务不能正确回滚

  1. @Service
  2. public class Service2 {
  3. @Autowired
  4. private AccountMapper accountMapper;
  5. @Transactional(rollbackFor = Exception.class)
  6. public void transfer(int from, int to, int amount) {
  7. try {
  8. int fromBalance = accountMapper.findBalanceBy(from);
  9. if (fromBalance - amount >= 0) {
  10. accountMapper.update(from, -1 * amount);
  11. new FileInputStream("aaa");
  12. accountMapper.update(to, amount);
  13. }
  14. } catch (FileNotFoundException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  • 原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
  • 解法1:异常原样抛出
    • 在 catch 块添加 throw new RuntimeException(e);
  • 解法2:手动设置 TransactionStatus.setRollbackOnly()
    • 在 catch 块添加 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

3. aop 切面顺序导致导致事务不能正确回滚

  1. @Service
  2. public class Service3 {
  3. @Autowired
  4. private AccountMapper accountMapper;
  5. @Transactional(rollbackFor = Exception.class)
  6. public void transfer(int from, int to, int amount) throws FileNotFoundException {
  7. int fromBalance = accountMapper.findBalanceBy(from);
  8. if (fromBalance - amount >= 0) {
  9. accountMapper.update(from, -1 * amount);
  10. new FileInputStream("aaa");
  11. accountMapper.update(to, amount);
  12. }
  13. }
  14. }
@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 基础组件

Spring - 图4

①用户发送请求至前端控制器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 执行流程

  • 准备阶段
  • 匹配阶段
  • 执行阶段

准备阶段

  1. 在 Web 容器第一次用到 DispatcherServlet 的时候,会创建其对象并执行 init 方法
  2. init 方法内会创建 Spring Web 容器,并调用容器 refresh 方法
  3. refresh 过程中会创建并初始化 SpringMVC 中的重要组件, 例如 MultipartResolver,HandlerMapping,HandlerAdapter,HandlerExceptionResolver、ViewResolver 等
  4. 容器初始化后,会将上一步初始化好的重要组件,赋值给 DispatcherServlet 的成员变量,留待后用

image.png

匹配阶段

  1. 用户发送的请求统一到达前端控制器 DispatcherServlet
  2. DispatcherServlet 遍历所有 HandlerMapping ,找到与路径匹配的处理器
  3. 将 HandlerMethod 连同匹配到的拦截器,生成调用链对象 HandlerExecutionChain 返回
  4. 遍历HandlerAdapter 处理器适配器,找到能处理 HandlerMethod 的适配器对象,开始调用

image.png

执行阶段

  1. 执行拦截器 preHandle
  2. 由 HandlerAdapter 调用 HandlerMethod
  3. 执行裹层没有异常:① 返回 ModelAndView② 执行拦截器 postHandle 方法③ 解析视图,得到 View 对象,进行视图渲染
  4. 出现异常:进入 HandlerExceptionResolver 异常处理流程
  5. 最后都会执行拦截器的 afterCompletion 方法

image.png

自定义拦截器和异常处理器

自定义拦截器
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 组成

  1. @SpringBootConfiguration 与普通 @Configuration 相比,唯一区别是前者要求整个 app 中只出现一次
  2. @ComponentScan
    • excludeFilters - 用来在组件扫描时进行排除,也会排除自动配置类
  3. @EnableAutoConfiguration 也是一个组合注解,由下面注解组成
    • @AutoConfigurationPackage – 用来记住扫描的起始包
    • @Import(AutoConfigurationImportSelector.class) 用来加载 META-INF/spring.factories 中的自动配置类

为什么不使用 @Import 直接引入自动配置类

有两个原因:

  1. 让主配置类和自动配置类变成了强耦合,主配置类不应该知道有哪些从属配置
  2. 直接用 @Import(自动配置类.class),引入的配置解析优先级较高,自动配置类的解析应该在主配置没提供时作为默认配置

因此,采用了 @Import(AutoConfigurationImportSelector.class)

  • AutoConfigurationImportSelector.class 去读取 META-INF/spring.factories 中的自动配置类,实现了弱耦合。
  • 另外 AutoConfigurationImportSelector.class 实现了 DeferredImportSelector 接口,让自动配置的解析晚于主配置的解析