静态资源映射

SpringMVC的前端控制器(DispatcherServlet)会拦截请求,配置是“/*”会拦截所有的请求。如果请求的url是”reg.html”,那么前端控制器会去Controller中找url相匹配的handler(处理器),而不是去调度静态页面,所以前端控制器会把静态资源过滤掉。

通过静态资源映射,告诉前端控制不要拦截静态资源。
在spring-mvc.xml中添加配置:

  1. <!-- 静态资源映射 -->
  2. <mvc:resources mapping="/css/**" location="/css/" />
  3. <mvc:resources mapping="/js/**" location="/js/"/>
  4. <mvc:resources mapping="/images/**" location="/images/"/>
  5. <mvc:resources mapping="/**" location="/"/>

中文处理

请求中和响应中常常有中文乱码的问题,springMVC提供了一个专门的字符过滤器,可以处理中文乱码。
我们只需要在web.xml中配置过滤器即可。

<!-- spring的中文过滤器 -->
<filter>
  <filter-name>CharacterEncodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  <!-- 默认是utf-8 我们可以通过初始化参数配置 -->
  <init-param>
    <param-name>encoding</param-name>
    <param-value>utf-8</param-value> 
  </init-param>
</filter>
<filter-mapping>
  <filter-name>CharacterEncodingFilter</filter-name>
  <url-pattern>/*</url-pattern> 
</filter-mapping>

我们使用的是tomcat7,tomcat8以后,tomcat本身的编码设置就已经是utf-8。tomcat7的编码设置是
iso8859-1。
上述方法为POST请求解决乱码方式,如果是GET请求,中文就会乱码。 所以,发送中文不要使用GET请求。

类型转换器

当前端请求中带有日期格式的参数时:
处理方案:

  1. 在实体类的属性的上面添加注解,配置日期格式:@DateTimeFormat(pattern="yyyy-MM-dd")
    1. 请求中的格式:1999-08-20;用实体类接收参数
  2. 使用形参接收日期数据时:

public String reg(String name,@DateTimeFormat(pattern="yyyy-MM-dd" Date birth){};

  1. 如果我们的系统中有大量的日期格式要处理,我们可以配置一个日期格式转换器。

我们自定义一个类型转换器: 实现接口org.springframework.core.convert.converter.Converter
tips:这个接口中的泛型S表示原始类型,T表示目标类型。

public class DateFormatConverter implements Converter<String,Date> {
    @Override
    public Date convert(String s) {
        try{
            return new SimpleDateFormat("yyyy-MM-dd").parse(s);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}
<!-- 开启MVC注解驱动 -->        
<mvc:annotation-driven conversion-service="conversionService" />
<!-- 配置各种转换器服务 -->
<bean class="org.springframework.format.support.FormattingConversionServiceFactoryBean" id="conversionService">
  <property name="converters">
    <set>
      <bean class="com.quail.utils.DateFormatConverter"/>
    </set>
  </property>
</bean>

测试:

@Controller
public class DateFormController {
    @RequestMapping("/dateForm")
    public void dateForm(String uname,Date date){
        System.out.println(uname+":"+date);
    }
}

image.png

拦截器

拦截器其实就是使用spring的AOP实现了一个请求处理的增强。类似filter
如果要实现拦截器需要实现一个接口:org.springframework.web.servlet.HandlerInterceptor
源码:

public interface HandlerInterceptor {
    /*** 在进入请求之前进行预处理(在进入请求之前执行),这个方法可以拦截请求的执行。 
    * @param request current HTTP request 
    * @param response current HTTP response 
    * @param handler 处理器对象 
    * @return {@code true} 如果返回true,表示放行,否则表示拦截
    */
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
        //我们可以使用request转发,也可以使用repsonse重定向。 
        return true; 
    }
    //请求处理之后,但是return之前。 这个方法不能拦截请求,但是可以修改这个请求最终去的方向和数据
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { 
        //如果我们将这里的modelAndView对象的视图名称修改了,那么这个请求最终就会进入我 们修改后的视图。 
    }
    //请求完全结束之后执行,扫尾工作。 释放资源,处理异常。
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }
}

自定义拦截器:

package com.quail.utils;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author quail
 * @date 2021/3/25 18:59
 * @TODO 自定义拦截器
 */
public class QuailInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行了preHandle拦截:"+handler);
        return true;//false阻断,true放行
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行了postHandle拦截");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行了afterCompletion拦截");
    }
}
<!-- 配置拦截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- 要拦截的请求路径 -->
            <mvc:mapping path="/*"/>
            <!-- 不拦截的请求路径 -->
            <mvc:exclude-mapping path="/*.html"/>
            <mvc:exclude-mapping path="/css/**"/>
            <mvc:exclude-mapping path="/js/**"/>
            <mvc:exclude-mapping path="/image/**"/>
            <mvc:exclude-mapping path="/doLogin"/> <!-- 配置我们自己的拦截器 -->
            <bean class="com.quail.utils.QuailInterceptor"/>
        </mvc:interceptor> 
      <!-- 还可以配置更多的拦截器 --> 
      <!-- <mvc:interceptor>--> 
      <!-- <mvc:mapping path=""/>--> 
      <!-- </mvc:interceptor>-->
    </mvc:interceptors>
  • 多个拦截器的执行顺序

image.png
image.png

统一异常处理器

项目中的异常处理?
编译期异常:try。。。catch。。。输出到控制台。
运行时异常:没处理。
异常不能直接“吞掉”。 就是在底层直接输出到控制台。
底层出现的异常,全部打包,并且抛到上层(controller)。
有的企业会有自己的开发API:
①自己的框架(大部分都是从开源框架重构的)
②自己一套自定义的异常。一般自己定义的异常都是运行时异常。
底层的编译期异常,要处理,并且包裹成运行期异常抛出。

spring提供了一个统一的异常处理器。我们需要自己实现接口:org.springframework.web.servlet.HandlerExceptionResolver

package com.quail.utils;

import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author quail
 * @date 2021/3/25 19:13
 * @TODO
 */
public class QuailExceptionHandler implements HandlerExceptionResolver {
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        //记录日志(文件,数据库,邮件)
        //发短信,发邮件 
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("ex", ex);
        return modelAndView;
    }
}

我们只要将这个类交个spring管理即可,spring会自动识别。

事务

原子性:一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
一致性:事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性:持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。

事务的隔离级别:
先看几个概念:
脏读:读取到别的事务尚未提交的数据。
不可重复读: 在同一次事务中重复读取的两次数据时不一致的,这种现象都是由于update操作导致的
幻读: 在同一次事务中,读取的同一批数据的数量不一致。这种现象都是因为insert/delete导致的

两类事务丢失的问题
[1]第一类事务丢失,是因为事务提交导致事务丢失。
A查看数据库金额是100,准备添加50进去。最终应该是150.
A 查询完成之后,在没有修改数据之前,B快速的修改了数据,将100修改为150,并且提交了数据。A线程在提交事务之后要保证数据库应该是150。 这样就导致B线程的事务丢失。
[2]第二类事务丢失,正好相反,是因为事务回滚导致事务丢失。

主要原因就是:隔离级别不够。

面试题:什么是事务?
事务就是一组操作。这些操作要么一起成功,要么一起失败。
我们常说的事务就是对数据库的一组操作,要么一起成功,要么回滚。

事务的4中隔离级别:
readuncommit:允许读取未提交的数据,必然会有脏读, 幻读,不可重复的现象。
readcommit:不允许读取未提交的数据,不会有脏读。 已然会有幻读和不可重复读的问题。(Oracle数据库的默认隔离级别)
readrepeatable:可重复,解决了脏读和不可重复读的问题,已然可能产生幻读。(MySQL默认隔离级别)
Serialization:串行化。同步执行。同一时刻,统一数据只有一个事务可以操作。可以解决所有问题。但是效率贼低。一般都不会使用。

编程式事务

public interface PlatformTransactionManager extends TransactionManager {
    //得到一个事务状态对象(开启一个事务)
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException; 
    //提交事务 
    void commit(TransactionStatus var1) throws TransactionException; 
    // 回滚事务 
    void rollback(TransactionStatus var1) throws TransactionException; }
}

Spring实现事务需要实现一个接口:TransactionStatus

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable { 
    //是否有保存点 
    boolean hasSavepoint(); 
    void flush(); 
    //父类的方法 
    //是否是一个新事务 
    boolean isNewTransaction(); 
    //是否只回滚 
    void setRollbackOnly(); 
    boolean isRollbackOnly(); 
    //是否已经完成
    boolean isCompleted(); 
    //还有很多其他的属性 
}

spring提供了这个事务处理的接口,不同的数据库有不同的事务管理器的实现。
MySQL数据库的事务管理器采用的是org.springframework.jdbc.datasource.DataSourceTransactionManager
在spring的配置文件中添加事务管理器和事务处理模板类配置:

<!-- 配置事务管理器 -->
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
  <!-- 注入数据源 -->
  <property name="dataSource" ref="datasource"/>
</bean> 
<!-- 编程式事务模板类 这个类是spring提供的 -->
<bean id="transactionTemplate" 
      class="org.springframework.transaction.support.TransactionTemplate"> 
  <!-- 注入事务管理器 -->
  <property name="transactionManager" ref="transactionManager"/>
  <property name="timeout" value="6000"/><!-- 超时时间 -->
  <property name="readOnly" value="false"/>
  <property name="isolationLevel" value="1"/>
  <!-- 事务的隔离级别,默认采用当前 使用的数据库的隔离级别 -1表示默认, 1,2 ,4,8 -->
</bean>

实现:

     @Autowired
    private TransactionTemplate transactionTemplate;

    @Override
    public int transfer(String fromName, String toName, int money) { 
        //lamda表达式实现函数式接口 
        return transactionTemplate.execute((transactionStatus)->{ 
        // 减第一个人的钱 
        empDao.subtraction(fromName,money); 
        // 手动出现一个错误 
        if(1==1) 
        throw new RuntimeException("故意出错"); 
        // 给第二个人加钱 
        empDao.addition(toName,money); return 1; 
        });
    }

Spring事务的传播特性

什么是传播特性?
事务的传播特性是说在嵌套的环节中,外层事务和里层事务的处理方案。
7中传播特性:
前提是:methodA调用methodB。传播特性是给methodB的配置。
[1] REQUIRED
必须有事务,如果当前的方法有事务,就支持,如果没有就新建。
methodB必须在事务中执行,如果methodA有事务,methodB就使用methodA的事务。如果methodA没有事务,methodB会自己新建一个事务在事务中执行。
[2]SUPPORTS
支持当前的事务,如果当前没有事务就在非事务状态下执行。
methodA如果有事务,methodB就在事务中执行,methodA没有事务,methodB就在非事务状态下执行。
[3]MANDATORY
要求必须有事务,如果没有就抛出异常。
methodB要求methodA必须有事务,如果没有,就抛出异常。
[4]REQUIRES_NEW
必须新建一个事务,如果当前事务存在,则挂起,如果不存在直接新建。
methodB必须新建事务,如果methodA存在事务,则先将methodA的事务挂起,再新建事务,如果methodA没有事务,则直接新建事务。
[5]NOT_SUPPORTED
必须在非事务状态下运行,如果当前事务存在,则直接挂起。
methodB必须在非事务状态下执行,如果methodA存在事务,直接挂起。
[6]NEVER
绝对不能有事务,如果当前事务存在,直接抛出异常。
methodB必须在非事务状态下执行,如果methodA存在事务,抛出异常。
[7]NESTED
嵌套事务。

<tx:method name="transfer" 
           isolation="DEFAULT"
           read-only="false" 
           rollback-for="java.lang.Throwable" 
           timeout="-1"
           propagation="REQUIRED"/>
<tx:method name="add" 
           isolation="DEFAULT" 
           read-only="false" 
           propagation="NOT_SUPPORTED"/>