SpringMVC

什么是SpringMVC?

含义:SpringMVC是一种基于Java实现的MVC设计模型的请求驱动类型的轻量级的web层框架,属于SpringFramework后续产品,已经融合在SpringWeb flow中。SpringMVC已经成为目前主流的MVC框架之一,并随着任凭3.0的发布,已经全面超越身体乳提升struts2,成为最优秀的MVC框架,他通过一套注解,让一个简单的Java类成为处理请求的控制器,而无需时间任何借口,同时还支持Restful编程风格的请求。

5.2 使用SpringMVC

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>5.2.18.RELEASE</version>
  5. </dependency>
package com.xxgc.spring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {
    @RequestMapping("/login")
    public String userLogin(){

        return "";
    }
}

在web.xml中配置前端控制器

<!--    配置SpringMVC的前端控制器-->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>

    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

SpringMVC的数据响应

  1. 页面跳转

    直接返回字符串

    通过ModelAndView对象返回
  2. 回写数据

    直接返回字符串

    返回对象或集合

在SpringMVC的各个组件中,处理器、映射器、处理适配器称为SpringMVC的三大组件。

使用了以上代码可以自动加载RequestMappingHandlerMapping(处理映射器),和RequestMappingHandlerAdapler(处理适配器),来替代注解处理器和适配器的配置。

使用了它,默认底层会集成Jackson进行对象或集合的son字符串转换。

SpringMVC 三个核心

页面@RequestMapping(“/userLogin”)注解

返回字符串,字符串内容为页面名称

一、

前缀后缀加载Spring-mvc.xml配置文件中
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--              视图解析器的前缀-->
              <property name="prefix" value="/WEB-INF/jsp/"></property>
<!--              视图解析器的后缀-->
              <property name="suffix" value=".jsp"></property>
       </bean>

二、返回数据

@ResponseBody

就不会走视图解析器,会直接返回数据。

如果想返回json格式需要导包
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>

需要在Spring-mvc.xml配置一句话
<!-- 添加MVC的注解驱动-->
<mvc:annotation-driven/>

返回数据的时候直接返回对象,SpringMVC会帮你解析成json
@RequestMapping("/userLogin9")
 @ResponseBody
 public Users userLogin9() {
    System.out.println("userLogin9 run...");
    Users users = new Users();
    users.setUId(1);
    users.setName("张三");
    users.setPass("123456");
    return users;
}

三、获取数据

3.1 获取请求参数

客户端请求的格式/login?name-value&pass-value....

服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的    参数:
  • 基本类型参数
  • POJO类型参数
  • 数组类型参数
  • 集合类型参数

3.2 基本类型参数

Controller中的业务方法的参数名称要与请求参数name一致,参数值会自动映射匹配。
@ResponseBody
@RequestMapping("/live1")
public void userLive(int liveId,String liveName){
    System.out.println("直播间ID = " + liveId);
    System.out.println("liveName = " + liveName);
}
http://localhost/live/live1?liveId=666&liveName=LOL

卸了参数不一定需要传递,可以传某个值,也可以完全不传,但是要注意,不传值会给一个null,如果是int类型会报错,应改为Integer。

3.3 POJO类型参数

Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。
@ResponseBody
@RequestMapping("/live2")
public Users userLive2(Users users){
    System.out.println(users);
    return users;
}
http://localhost:8080/live/live2?uId=123&name=lisi&pass=123456

在对象中,不需要传递所有参数,也可以不传,不传递会显示null或默认值。

3.4 数组类型参数

Controller中的业务方法数组名称与请求参数name一致,参数会自动映射匹配。

@ResponseBody
@RequestMapping("/live3")
public String[] userLive3(String[] names){
    System.out.println(Arrays.asList(names));
    return names;
}

传递参数时,必须保证key的名称和方法参数名一致,否则拿不到数据,属性类型必须一致。

3.5 集合类型参数

获取集合数据时,要将集合参数包装到一个POJO(bean包)中才可以

@Data
public class UserExtend {
    private List<Users> userList;
}
@ResponseBody
@RequestMapping("/live4")
public void userLive4(UserExtend ue){
    for (Users users : ue.getUserList()) {
        System.out.println("users = " + users);
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
登录${msg}
<form action="${pageContext.request.contextPath}/live/live4" method="post">
    <input type="text" name="userList[0].uId"><br>
    <input type="text" name="userList[0].pass"><br>
    <input type="text" name="userList[0].name"><br>
    <input type="text" name="userList[1].uId"><br>
    <input type="text" name="userList[1].pass"><br>
    <input type="text" name="userList[1].name"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

集合类型参数二

当使用Ajax提交时,可以指定contentType为json格式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用POJO进行包装。

@ResponseBody
@RequestMapping("/live5")
public void userLive5(@RequestBody List<Users> users){
    for (Users user : users) {
        System.out.println("user = " + user);
    }
}
[
    {
      "uId": "1",
      "name": "zhangsan",
      "pass": "123456"
    },
    {
        "uId": "2",
      "name": "lisi",
      "pass": "654321"
    }

调试工具改

把body调成Application/json

ajax改

Context-Type = json

四、静态资源

方式一

<!-- 开放静态资源都访问 -->
<mvc:resources mapping="/js/**" location="/js/"/>

如果你想拿到静态资源,必须添加以上代码,给SpringMVC一个映射关系。

方式二

如果加了这句话SpringMVC没有找到静态资源,会让容器(Tomcat)来找。

注意:加上过后,所有的静态资源都会被用户看见,不想让用户看见的文件放在WEB-INF下面

注意:

  1. 通常情况下我们把不容易发生改变的静态资源放在web文件夹下的CDN文件夹下。
  2. 需要保密的静态资源放在WEB-INF下,通过@RequestMapping获取

五、解决中文乱码问题

解决方案一

在web.xml中添加

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <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>

解决方案二

在spring-mvc.xml中添加

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="defaultCharset" value="UTF-8"/>
            <property name="writeAcceptCharset" value="false"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

六、请求参数名和字段不一样

使用场景:当某些字段需要隐藏其真实含义需要使用@requestParam

解决:@requestParam来更改默认参数

有三个参数

  1. value修改参数名称 必填 可省略value
  2. required 是否设置为必须携带的参数 默认为true 非必填
  3. defaultValue 没有传递参数时的默认值,写上过后required失效 非必填

八、自定义类型转换器

什么是自定义转换器?

Spring MVC默认已经提供了一些常用的类型转换器,列入客户端提交的字符串转换为int 或 long进行参数设置。但是不是所有的数据类型都提供了转换器,没有提供转换器的就需要自定义转换器,例如:时间类型的数据就需要自定义转换器。

实现步骤:

  1. 定义一个转换器实现Converter借口
  2. 在配置文件中声明转换器
  3. 在中引用

8、方法中传递原生的对象

使用了SpringMVC后,框架帮我们将原生的Request,Response,Session对象封装起来了,当然我们仍然可以使用这些原生的对象,只需要在方法的参数列表中声明对应的对象类型即可:

@RequestMapping("/testObject")
public String testObject(HttpServletRequest request,
                         HttpServletResponse response,
                         HttpSession session) {
    System.out.println("request = " + request);
    System.out.println("response = " + response);
    System.out.println("session = " + session);
    return "welcome";
}

9. 获取请求头的信息

使用@RequestHeader注解放在方法的参数前,可以获取请求头的信息。

@RequestMapping("/header")
public String getRequestHeader(@RequestHeader("Accept") String accept){
    System.out.println("accept = " + accept);
    return "welcome";
}

@RequestHeader注解中的参数如下:

参数 作用
value 请求头Key值
name 请求头Key值,与value同义,语义化的体现
required 是否必须携带这个请求头信息,默认是true
defaultValue 默认值,默认为空

5.3.4 文件上传

文件上传有三个必要条件:

  1. 表单提交方式必须为post提交
  2. 必须使用表单提交
  3. 需要在form表单中添加一个参数enctype=”multipart/form-data”
<form action="/file/upload" method="post" enctype="multipart/form-data">
      <input type="file" name="upload" placeholder="请选择文件"><br>
      <button type="submit">立即上传</button>
</form>

首先我们需要引入commons-fileupload依赖和commons-io

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

在spring-mvc.xml中配置文件上传解析器

七、拦截器

7.1 什么是拦截器?

SpringMVC的拦截器类似于Servlet的过滤器Filter,用于对请求处理器进行预处理和后处理。

将拦截器按一定的顺序结成一条链,这条链成为拦截器链(Interceptor Chain),在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。

7.2 拦截器和过滤器的区别

区别 过滤器 拦截器
使用范围 是servlet规范中的一部分,任何JavaWeb工程都可以使用 是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才可以使用
拦截范围 在url-pattern中配置/*之后,可以对所有要访问的资源拦截 中配置/**之后,也可以对所有资源进行拦截,通过标签排除不需要拦截器的资源

7.3 拦截器的实现

package com.xxgc.spring.interceptor;


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

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

public class MyInterceptor implements HandlerInterceptor {
    //在目标方法之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return false;
    }
    //在目标方法之后执行,在试图对象返回之前执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }
    //在流程都执行完毕过后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

在真实项目中,一般我们会对每个请求进行采样,当样本到达一定数量过后,进行系统性的分析。

比如:接口A-B端耗时,B-C端耗时,或某个接口的错误率

A-B端耗时大的解决方法:

  • 在A基数大的地区部署服务器
  • 增加服务器带宽
  • A端上传进行压缩

B-C端耗时大的解决方法

  • 升级服务器
  • 优化代码

C-D耗时大的解决方法

  • 压缩静态资源
  • 增加服务器带宽
  • 提示用户升级电脑配置

7.5 SpringMVC异常处理

什么是异常?

在系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。

系统的Dao、Service、Controller出现都通过throws Exception向上抛出,最后由SpringMVC前端控制器交出由异常处理器进行异常处理。

7.6 使用SpringMVC异常处理两种方式

  1. 使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver
  2. 实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器

2.切点表达式

 execution([修饰符] 返回类型 包名.类名.方法名(参数))
execution(public boolean com.xxgc.aop.spring.UserLoginSpring.userLogin())
execution(boolean com.xxgc.aop.spring.UserLoginSpring.userLogin(String,String))
execution(boolean com.xxgc.aop.spring.UserLoginSpring.*(..))
execution(com.xxgc.aop.spring.UserLoginSpring.get*(..))
execution(* com.xxgc.aop.spring.*.*(..))
execution(* com.xxgc.aop.spring..*.*(..))
execution(* *..*.*(..))
  1. 完整路径无参数的写法
  2. 有参完整路径
  3. 布尔返回类型 类下面的所有方法 参数不限
  4. 所有返回类型 且方法名为get开头 参数不限
  5. Spring包下面的所有类的所有方法
  6. Spring包下面的包下面的…任意方法
  7. 所有方法

3.通知类型

<aop:config>
    <aop:aspect ref="切面类">
        <aop:before method="通知方法名称" pointcut="切点表达式"></aop:before>
    </aop:aspect>
</aop:config>

通知类型的配置语法:

<aop:通知类型 method="切面类的方法" pointcut="切点表达式"></aop:通知类型>
<!--前置 -->
<aop:before method="before" pointcut="execution(* com.xxgc.aop.spring.UserLoginSpring.*(..))"></aop:before>
<!--后置 -->
<aop:after-returning method="afterReturning" pointcut="execution(* com.xxgc.aop.spring.UserLoginSpring.*(..))"></aop:after-returning>
<!--最终 -->
<aop:after method="before" pointcut="execution(* com.xxgc.aop.spring.UserLoginSpring.*(..))"></aop:after>
<!--环绕执行 -->
<aop:around method="around" pointcut="execution(* com.xxgc.aop.spring.UserLoginSpring.*(..))"></aop:around>
<!--抛出异常 -->
<aop:after-throwing method="throwing" pointcut="execution(* com.xxgc.aop.spring.UserLoginSpring.*(..))"></aop:after-throwing>

切点表达式的抽取

定义

<!--共用的切点表达式-->
<aop:pointcut id="userLogin" expression="execution(* com.xxgc.aop.spring.UserLoginSpring.*(..))"></aop:pointcut>

引用

<aop:before method="before" pointcut-ref="userLogin"></aop:before>

8.7 基于注解的AOP开发

1.开发步骤
  1. 创建目标接口和目标类(内部有切点)
  2. 创建切面类(内部有通知、增强方法)
  3. 将目标类和切面类的对象创建权给Spring
  4. 在切面类中使用注解配置织入关系
  5. 在配置文件中开启组件扫描和AOP的自动代理
  6. 测试

目标接口

package com.xxgc.spring.service.impl;

import com.xxgc.spring.POJO.User;
import com.xxgc.spring.service.IUserLogin;
import org.springframework.stereotype.Service;

import java.util.List;
//把业务逻辑托管给Spring进行管理
@Service
public class UserLoginService implements IUserLogin {

    public int updateById(User user) {
        System.out.println("修改用户");
        return 0;
    }

    public int insertUser(User user) {
        System.out.println("添加用户");
        return 0;
    }

    public List<User> selectUserByName(String name) {
        System.out.println("根据用户名查询用户");
        return null;
    }

    public List<User> selectUserById(long uId) {
        System.out.println("根据用户ID查询");
        return null;
    }
}

切面类

package com.xxgc.spring.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component //把这个类交给Spring
@Aspect //告诉Spring这是一个切面类
public class UserLoginAspect {

    @Before("execution(* com.xxgc.spring.service.impl.UserLoginService.*(..))")
    public void before(){
        System.out.println("前置增强");
    }

}

开启组件扫描和AOP自动代理

package com.xxgc.spring.service;

import com.xxgc.spring.POJO.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserLoginTest {

        @Autowired
        private IUserLogin iul;

        @Test
        public void test(){
            iul.insertUser(new User());
        }

}

2. 注解通知类型

通知的配置语法:@通知注解(“切点表达式”)

名称 注解 说明
前置通知 @Before 前置通知,指定增强方法在切入点前执行
后置通知 @AfterReturning 后置通知,指定增强方法在切入点后执行
环绕通知 @Around 环绕通知,指定增强方法在切入点前后都执行
异常通知 @AfterThrowing 异常通知,指定方法抛出异常时执行
最终通知 @After 最终通知,无论增强方法执行是否有异常都会执行

3. 抽取切点表达式

//抽出切点表达式
@Pointcut("execution(* com.xxgc.spring.service.impl.UserLoginService.*(..))")
    public void myPoint(){

    }
//引用切点表达式
    @Before("UserLoginAspect.myPoint()")
    public void before(){
        System.out.println("前置增强");
    }

九、Spring事务

9.1 什么是事务?

事务必须服从ACID原则。ACID指的是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。通俗理解,事务其实就是一系列指令的集合。

原子性:操作这些指令时,要么全部执行成功,要么全部不自信。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。

一致性:事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。

隔离性:在该事务执行的过程中,无论发生的任何数据的改变都应该只存在于该事务之中,对外界不存在任何影响。只有在事务确定正确提交之后,才会显示该事务对数据的改变,其他事务才能获取到这些改变后的数据。

持久性:但事务正确完成后,它对于数据的改变是永久性的。

9.2 了解Spring事务的三个对象

1.PlatformTransactionManager

PlatformTransactionManager接口是Spring的事务管理器,它里面提供了我们常用的事务的方法。

方法 说明
TranscationStatus getTransaction(TransactionDefination defination) 获取事务的状态信息
void commit(Transaction status) 提高事务
void rollback(Transaction status 回滚事务

注意:

PlatformTransactionManager是接口类型,不同的Dao层技术则有不同的实现类,例如:
Dao层技术是jdbc或mybatis时:org.springframework.jdbc.datasource.DataSourceTransactionManager
Dao层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager

2.TransactionDefinition

TransactionDefinition是事务的定义信息对象,里面有如下方法:

方法 说明
int getIsolationLevel() 获得事务的隔离级别
int getPropogationBahavior() 获得事务的传播行为
int getTimeout() 获得超时时间
boolean isReadOnly() 是否仅读

事务隔离级别

设置事务隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。

ISOLATION_DEFAULT 默认
ISOLATION_READ_UNCOMMITTED 读未提交
ISOLATION_READ_COMMITTED 读已提交
ISOLATION_PEPEATABLE_READ 可重复读
ISOLATION_SERIALIZABLE 序列化

事务的传播行为

REQUIRED:如果没有事务,就新建一个事务,如果已经存在一个事务,加入到这个事务中,一般的选择(默认值)

SUPPORTS:支持当前事务,如果当前没有事务,就以非事务的方式执行(没有事务)

MANDATORY:使用当前事务,如果当前没有事务,就当前事务挂起

REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起

NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起

NEVER:以非事务方式运行,如果当前存在事务,跑出异常

NESTED:如果当前存在事务,则在嵌套事务内部执行。如果当前没有事务,则执行REQUIRED类似的操作

超时时间:默认值是-1,没有限制时间。如果有,以秒为单位进行设置

是否仅读:建议查询时设置为仅读

3.TransactionStatus

TransactionStatus接口提供的是事务具体的运行状态,方法介绍如下:

方法 说明
boolean hasSavepoint() 是否存储回滚点
boolean isCompleted() 事务是否完成
boolean isNewTransaction() 是否是新事务
boolean isRollbackOnly 事务是否回滚

9.3 声明式事务

Spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说声明,就是在指配置文件中声明,用在Spring配置文件中声明式的处理事务来代替代码式的处理事务。

声明式事务处理的作用

事务管理事务不入侵开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中。事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策略的话,也只需要在定义文件中重新配置即可。

在不需要事务管理的时候,只有在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便

“注意:Spring声明式事务控制就是用的AOP”

@Transactional(timeout=30)//默认是30秒