一、第一个Hello
1.1 注解集成版
测试、日志、Thymeleaf、webmvc、servletAPI、Tomcat
1.1.1 项目结构
1.1.2 依赖项
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.46</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
1.1.3 logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="INFO">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT"/>
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<!-- <logger name="mapper.EmpMapper" level="DEBUG"/>-->
<logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG" />
<logger name="controller" level="DEBUG" />
</configuration>
1.1.4 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--1.注册servlet-->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--通过初始化参数指定SpringMVC配置文件的位置,进行关联-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springConfig/springmvc-config.xml</param-value>
</init-param>
<!-- 启动顺序,数字越小,启动越早 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--所有请求都会被springmvc拦截 -->
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
1.1.5 spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 处理映射器 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- 处理适配器 -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
<context:component-scan base-package="controller"/>
<!-- 让Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
<!-- Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
</beans>
1.1.6 Controller
package controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
/**
* @author 13544
*/
@Controller
public class PortalController {
private Logger logger =LoggerFactory.getLogger(this.getClass().getName());
@RequestMapping(value = "/portal", method = {RequestMethod.GET})
public ModelAndView portalView(ModelAndView mv) {
logger.debug("进入了PortalServlet的indexView....");
mv.addObject("username", "lijiamin");
//封装要跳转的视图,放在ModelAndView中,这里涉及到了逻辑视图的知识点
mv.setViewName("portal");
return mv;
}
@RequestMapping(value = "/", method = {RequestMethod.GET})
public ModelAndView indexView(ModelAndView mv) {
logger.debug("进入了IndexServlet的indexView....");
//封装要跳转的视图,放在ModelAndView中,这里涉及到了逻辑视图的知识点
mv.setViewName("index");
return mv;
}
}
1.1.7 html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
hello
<br>
<a th:href="@{/portal}">跳转到portal-2</a>
</body>
</html>
---------------------------------------------------------------------------
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>跳转页</title>
</head>
<body>
<h1 th:text="${username}">text</h1>
</body>
</html>
1.2 配置文件版
- 配置前端控制器,注册DispatcherServlet
- 创建SpringMVC配置文件,注册映射器信息springmvc-servlet.xml
- 创建控制器类HelloControlloer01.java,实现跳转等基本操作
- 启动tomcat,在地址栏输入/hello1,进行跳转测试
1.2.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>4_SPringMVC_test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>4_SPringMVC_test Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging-api</artifactId>
<version>1.1</version>
</dependency>
<!-- 这东西有点重要 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.46</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.46</version>
</dependency>
</dependencies>
<build>
<finalName>4_SPringMVC_test</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
1.2.2 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--配置前端控制器-->
<!--1.注册DispatcherServlet-->
<servlet>
<servlet-name>springMVC2</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--关联一个springMVC的配置文件:【servlet-name】-servlet.xml-->
<!--默认去WEB-INF目录中去寻找-->
<!-- 默认寻找<servlet-name>元素文本内容-servlet.xml文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:servlet/springmvc-servlet.xml</param-value>
</init-param>
<!--启动级别-1,在Tomcat启动时就初始化Spring容器-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--/ 匹配所有的请求;(不包括.jsp)-->
<!--/* 匹配所有的请求;(包括.jsp)-->
<!-- 这里我踩了坑,404,因为要和servlet文件的beanID保持一致 -->
<servlet-mapping>
<servlet-name>springMVC2</servlet-name>
<url-pattern>/hello1</url-pattern>
</servlet-mapping>
</web-app>
1.2.3 springmvc-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置1:处理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--配置2:处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!-- 配置3:视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/view/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!--配置3:视图解析器-->
<!-- <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
</bean>-->
<!-- 配置静态资源的访问映射,此配置中的文件将不被前端控制器拦截 -->
<!--<mvc:resources mapping="" location=""></mvc:resources>-->
<!--配置4:处理器-->
<!--通过处理器映射器在url中寻找id为hello的jsp-->
<bean id="/hello1" class="ssm.controller.HelloController01"/>
</beans>
1.2.4 HelloController01.java
package ssm.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author 13544
*/
public class HelloController01 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
System.out.println("handleRequest..");
//ModelAndView 模型和视图
ModelAndView mv = new ModelAndView();
//封装对象,放在ModelAndView中。Model
mv.addObject("msg", "HelloSpringMVC!");
//封装要跳转的视图,放在ModelAndView中,这里涉及到了逻辑视图的知识点
mv.setViewName("test_jsp");
return mv;
}
}
1.2.5 test_jsp.jsp
<html>
<body>
<h2>Hello World!</h2>
${msg}
</body>
</html>
1.3 注解普通版
- 配置web.xml,实现:Servlet注册+请求拦截
- 配置springmvc-config.xml,实现:处理器配置+注解包扫描+视图解析器
- 配置后端控制器类,完成请求拦截转发
1.3.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>4_SPringMVC_test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>4_SPringMVC_test Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging-api</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.46</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.46</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.8</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<finalName>4_SPringMVC_test</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
1.3.2 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--1.注册servlet-->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--通过初始化参数指定SpringMVC配置文件的位置,进行关联-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springConfig/springmvc-config.xml</param-value>
</init-param>
<!-- 启动顺序,数字越小,启动越早 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--所有请求都会被springmvc拦截 -->
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
1.3.3 springmvc-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 处理映射器 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- 处理适配器 -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
<context:component-scan base-package="controller"></context:component-scan>
<!-- 让Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler/>
<!-- 配置静态资源的访问映射,此配置中的文件将不被前端控制器拦截 -->
<!--<mvc:resources mapping="" location=""></mvc:resources>-->
<!--
支持mvc注解驱动
在spring中一般采用@RequestMapping注解来完成映射关系
要想使@RequestMapping注解生效
必须向上下文中注册DefaultAnnotationHandlerMapping
和一个AnnotationMethodHandlerAdapter实例
这两个实例分别在类级别和方法级别处理。
而annotation-driven配置帮助我们自动完成上述两个实例的注入。
-->
<mvc:annotation-driven/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="InternalResourceViewResolver">
<!-- 前后缀 -->
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".html"/>
</bean>
<!-- 视图解析器 -->
<!-- <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"></bean>-->
</beans>
1.3.4 HelloController01.java
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
/**
* 首页跳转,主页视图
*
* @author 13544
*/
@Controller
public class IndexServlet {
@RequestMapping(value = "/",method = {RequestMethod.GET})
public ModelAndView indexView(ModelAndView mv) {
System.out.println("进入了IndexServlet的indexView....");
//封装要跳转的视图,放在ModelAndView中,这里涉及到了逻辑视图的知识点
mv.setViewName("index");
return mv;
}
}
如果方法中添加了Model参数,那么每次调用该请求处理方法时,Spring MVC都会创建Model对象,并将其作为参数传递给方法。
1.4 流程总结
二、常见注解玩法
2.1 @RequestMapping
在方法上可以标注
@RestController,是一个Controller和ResponseBody的增强版@RestControllerAdvice
2.1.1 注解的属性
2.1.2 组合注解
其实是一种用来简化上面代码的一种东西
@GetMapping:匹配Get方式请求
@PostMapping:匹配Post方式请求
@PutMapping:。。。。
@DeleteMapping:。。。。
@PatchMapping:。。。。
@GetMapping(value = "/HelloController01") 其实相当于 @RequestMapping(value = "/HelloController01",method = RequestMethod.GET) ,是一种简写
2.1.3 匹配方式
- 精确匹配:@RequestMapping("/say/hello/to/spring/mvc")
- 模糊匹配:@RequestMapping("/fruit/*")
2.1.4 POST请求的字符乱码问题
到 web.xml 中配置 CharacterEncodingFilter 即可
<!-- 配置过滤器解决 POST 请求的字符乱码问题 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- encoding参数指定要使用的字符集名称 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 请求强制编码 -->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!-- 响应强制编码 -->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
TIP
注1:在较低版本的 SpringMVC 中,forceRequestEncoding 属性、forceResponseEncoding 属性没有分开,它们是一个 forceEncoding 属性。这里需要注意一下。
注2:由于 CharacterEncodingFilter 是通过 request.setCharacterEncoding(encoding); 来设置请求字符集,所以在此操作前不能有任何的 request.getParameter() 操作。在设置字符集之前获取过请求参数,那么设置字符集的操作将无效。
2.2 @RequestParam
2.2.1 注解的属性
注:如果形参上设置的参数在请求上没有传过来,默认会报错400,需要用required关闭默认必须要有请求参数
2.2.2 普通参数接收
有时前端请求中参数名和后台控制器类方法中的形参名不一样,就会导致后台无法正确绑定并接收到前端请求的参数。为此,Spring MVC提供了@RequestParam注解来进行间接数据绑定
@RequestMapping(value = "/hello")
public ModelAndView hello(@RequestParam(value = "helloId") Integer id, ModelAndView mv) {
System.out.println(id);
mv.setViewName("index");
return mv;
}
输入url:http://localhost:8080/07_test/hello?helloId=234,就能看见控制台输出了数字234
2.2.3 对象参数接收
- 创建一个实体类 UserInfo
- 控制器传入参数选择形参对象
@Controller
@RequestMapping(value = "/HelloController01")
public class HelloController01 {
@RequestMapping(value = "/say2", method = {RequestMethod.POST})
public String sayAno3(UserInfo user) {
System.out.println(user);
return "/WEB-INF/view/test.jsp";
}
- html页面提交表单进行测试,控制台则输出相应字段
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页标题</title>
</head>
<body>
<h1>第一个首页</h1>
<!-- 这里的name要跟实体类字段保持一致 -->
<form action="/HelloController01/say2" method="post">
用户名<input type="text" name="userName"/></br>
密码<input type="text" name="userPwd"/></br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>
2.2.4 数组参数接收
如果前端是一组名称相同的数据,那么就需要使用数组或者集合进行接收,而不是用JavaBean直接拿过来
@RequestMapping(value = "/hello")
public ModelAndView hello(Integer[] ids , ModelAndView mv) {
System.out.println(id);
mv.setViewName("index");
return mv;
}
就类似与接收对象一样,这里放一个同前端传参名的数组就好了
2.2.5 集合参数接收
表单
<form th:action="@{/param/one/name/multi/value}" method="post">
请选择你最喜欢的球队:
<input type="checkbox" name="team[0]" value="Brazil"/>巴西
<input type="checkbox" name="team" value="German"/>德国
<input type="checkbox" name="team" value="French"/>法国
<input type="checkbox" name="team" value="Holland"/>荷兰
<input type="checkbox" name="team" value="Italian"/>意大利
<input type="checkbox" name="team" value="China"/>中国
<br/>
<input type="submit" value="保存"/>
</form>
方法
@RequestMapping("/param/one/name/multi/value")
public String oneNameMultiValue(
// 在服务器端 handler 方法中,使用一个能够存储多个数据的容器就能接收一个名字对应的多个值请求参数
@RequestParam("team") List<String> teamList
) {
for (String team : teamList) {
logger.debug("team = " + team);
}
return "target";
}
2.2.6 Map参数接收
忘记写了
2.3 @RequestHeader
通过这个注解获取请求消息头中的具体数据
@RequestMapping("/request/header")
public String getRequestHeader(
// 使用 @RequestHeader 注解获取请求消息头信息
// name 或 value 属性:指定请求消息头名称
// defaultValue 属性:设置默认值
@RequestHeader(name = "Accept", defaultValue = "missing") String accept
) {
logger.debug("accept = " +accept);
return "target";
}
2.4 @CookieValue
获取当前请求中的 Cookie 数据
@RequestMapping("/request/cookie")
public String getCookie(
// 使用 @CookieValue 注解获取指定名称的 Cookie 数据
// name 或 value 属性:指定Cookie 名称
// defaultValue 属性:设置默认值
@CookieValue(value = "JSESSIONID", defaultValue = "missing") String cookieValue,
// 形参位置声明 HttpSession 类型的参数即可获取 HttpSession 对象
HttpSession session
) {
logger.debug("cookieValue = " + cookieValue);
return "target";
}
2.5 @PathVariable
如果我们想要获取链接地址中的某个部分的值,就可以使用 @PathVariable 注解
@PathVariable(value = "movieId")
2.6 Json交互
json数据交互依赖包
@RequestBody:方法形参
@ResponseBody:类或方法
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
三、其他基础功能
3.1 页面跳转控制
3.1.1 方式一:返回String
转发
@RequestMapping("/test/forward/command")
public String forwardCommand() {
// 需求:要转发前往的目标地址不在视图前缀指定的范围内,
// 通过返回逻辑视图、拼接前缀后缀得到的物理视图无法达到目标地址
// 走视图解析器,但是不会自动添加前后缀了
// 这个是WEB-INF之外的转发
return "forward:/outter.html";
// 转发到指定的地址:
return "forward:/WEB-INF/outter.html";
}
重定向
@RequestMapping("/test/redirect/command")
public String redirectCommand() {
// 重定向到指定的地址:
// 这个地址由 SpringMVC 框架负责在前面附加 contextPath,所以我们不能加,我们加了就加多了
// 框架增加 contextPath 后:/demo/outter.html
// 我们多加一个:/demo/demo/outter.html
return "redirect:/outter.html";
}
3.1.2 方式二:ModeAndView
@Controller
public class PortalController {
@RequestMapping(value = "/", method = {RequestMethod.GET})
public ModelAndView indexView(ModelAndView mv) {
//封装要跳转的视图,放在ModelAndView中
mv.setViewName("index");
return mv;
}
}
3.2 获取原生Servlet API对象
在形参上把东西写上去就完了
@RequestMapping("/original/api/direct")
public String getOriginalAPIDirect(
// 有需要使用的 Servlet API 直接在形参位置声明即可。
// 需要使用就写上,不用就不写,开发体验很好,这里给 SpringMVC 点赞
HttpServletRequest request,
HttpServletResponse response,
HttpSession session
) {
logger.debug(request.toString());
logger.debug(response.toString());
logger.debug(session.toString());
return "target";
}
或者通过容器注入
// 获取ServletContext对象的方法二:从 IOC 容器中直接注入
@Autowired
private ServletContext servletContext;
@RequestMapping("/original/servlet/context/second/way")
public String originalServletContextSecondWay() {
logger.debug(this.servletContext.toString());
return "target";
}
3.3 属性域
3.3.1 请求域
①使用 Model 类型
@RequestMapping("/attr/request/model")
public String testAttrRequestModel(
// 在形参位置声明Model类型变量,用于存储模型数据
Model model) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
model.addAttribute("requestScopeMessageModel","i am very happy[model]");
return "target";
}
②使用 ModelMap 类型
@RequestMapping("/attr/request/model/map")
public String testAttrRequestModelMap(
// 在形参位置声明ModelMap类型变量,用于存储模型数据
ModelMap modelMap) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
modelMap.addAttribute("requestScopeMessageModelMap","i am very happy[model map]");
return "target";
}
③使用 Map 类型
@RequestMapping("/attr/request/map")
public String testAttrRequestMap(
// 在形参位置声明Map类型变量,用于存储模型数据
Map<String, Object> map) {
// 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
// 存入请求域这个动作也被称为暴露到请求域
map.put("requestScopeMessageMap", "i am very happy[map]");
return "target";
}
④使用原生 request
@RequestMapping("/attr/request/original")
public String testAttrOriginalRequest(
// 拿到原生对象,就可以调用原生方法执行各种操作
HttpServletRequest request) {
request.setAttribute("requestScopeMessageOriginal", "i am very happy[original]");
return "target";
}
⑤使用 ModelAndView
@RequestMapping("/attr/request/mav")
public ModelAndView testAttrByModelAndView() {
// 1.创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
// 2.存入模型数据
modelAndView.addObject("requestScopeMessageMAV", "i am very happy[mav]");
// 3.设置视图名称
modelAndView.setViewName("target");
return modelAndView;
}
3.3.2 会话域
使用会话域最简单直接的办法就是使用原生的 HttpSession 对象
@RequestMapping("/attr/session")
public String attrSession(
// 使用会话域最简单直接的办法就是使用原生的 HttpSession 对象
HttpSession session) {
session.setAttribute("sessionScopeMessage", "i am haha ...");
return "target";
}
3.3.3 应用域
应用域同样是使用原生对象来操作:
@Autowired
private ServletContext servletContext;
@RequestMapping("/attr/application")
public String attrApplication() {
servletContext.setAttribute("appScopeMsg", "i am hungry...");
return "target";
}
四、RESTFul
REST:Representational State Transfer,表现层资源状态转移
4.1 基本用法
REST 风格主张在项目设计、开发过程中,具体的操作符合 HTTP 协议定义的请求方式的语义
| 操作 | 请求方式 |
|---|---|
| 查询操作 | GET |
| 保存操作 | POST |
| 删除操作 | DELETE |
| 更新操作 | PUT |
过去做增删改查操作需要设计4个不同的URL,现在一个就够了。
| 操作 | 传统风格 | REST 风格 |
|---|---|---|
| 保存 | /CRUD/saveEmp | URL 地址:/CRUD/emp 请求方式:POST |
| 删除 | /CRUD/removeEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:DELETE |
| 更新 | /CRUD/updateEmp | URL 地址:/CRUD/emp 请求方式:PUT |
| 查询(表单回显) | /CRUD/editEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:GET |
4.2 四种请求方式映射
前置条件
<!-- RESTFul过滤器 -->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--解决Post表单提交中文乱码问题的过滤器 -->
<filter>
<filter-name>EncodingFilter</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>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
4.21 查GET请求
4.2.1 方法一:参数写死
控制器
@RequestMapping(value = "/getRestful/{movieId}", method = {RequestMethod.GET})
public ModelAndView getRestful(@PathVariable(value = "movieId", required = false) Integer id, ModelAndView mv) {
System.out.println("id" +
"是:" + id);
return new ModelAndView("redirect:/");
}
html
<form th:action="@{/getRestful/3}" method="GET">
<button type="submit">点击开始查询</button>
</form>
4.2.2 方法二:动态传参
html
<td>
<!-- restful:get -->
<a th:href="@{/getRestful/} + ${movie.movieId} +'/'+ ${movie.movieName}">
<button>GETid</button>
</a>
</td>
Java
@RequestMapping(value = "/getRestful/{movieId}/{movieName}", method = {RequestMethod.GET})
public ModelAndView getRestful(@PathVariable(value = "movieId", required = false) Integer id,
@PathVariable(value = "movieName", required = false) String movieName,
ModelAndView mv) {
System.out.println(id + " and " + movieName);
return new ModelAndView("redirect:/");
}
4.2.2 增POST请求
跟以前一样没变化,就from表单用post提交
4.2.3 改PUT请求
表单
- 要点1:原请求方式必须是 post
- 要点2:新的请求方式名称通过请求参数发送
- 要点3:请求参数名称必须是_method
- 要点4:请求参数的值就是要改成的请求方式
<!-- restful:PUT -->
<!-- 原请求方式必须是 post -->
<form th:action="@{/putRestful}" method="post">
<!-- 通过表单隐藏域携带一个请求参数 -->
<!-- 请求参数名:_method -->
<!-- 请求参数值:put -->
<input type="hidden" name="_method" value="put"/>
id:<input type="text" name="id">
name:<input type="text" name="name">
<button type="submit">更新</button>
</form>
控制器
// PUT
@RequestMapping(value = "/putRestful", method = {RequestMethod.PUT})
public ModelAndView putRestful(ModelAndView mv, Integer id, String name) {
System.out.println(id + " and " + name);
return new ModelAndView("redirect:/");
}
4.2.4 删DELETE请求
1、from通用表单要放在div外,可以放在body内的最下面
2、js引入的逻辑方法放在body内的最下面
通用表单
<!-- 通用表单,作用是将GET请求的删除操作转为DELETE-->
<form id="form1" action="" method="post">
<input type="hidden" name="_method" value="delete">
</form>
删除
<a th:href="@{|/de/${movie.movieId}|}" @click="confirmRemoveMovie">
删除
</a>
JS
new Vue({
el: "#tbody1",
methods: {
"reomveMovie": function (event) {
//获取通用表单
var formElem = document.getElementById("form1");
//修改通用表单的action
formElem.action = event.target.href;
//提交表单
formElem.submit();
//禁用目标对象的默认行为
event.preventDefault();
},
"confirmRemoveMovie": function (event) {
var flag = window.confirm("您确认要删除该影片信息吗?")
if (flag) {
//获取通用表单
var formElem = document.getElementById("form1");
//修改通用表单的action
formElem.action = event.target.href;
//提交表单
formElem.submit();
}
//禁用目标对象的默认行为
event.preventDefault();
}
}
})
;
Java
// 删除
@RequestMapping(value = "/de/{movieId}", method = {RequestMethod.DELETE})
public ModelAndView deleteId(ModelAndView mv, @PathVariable(value = "movieId") Integer movieId) {
System.out.println("id是:" + movieId);
String str = Integer.toString(movieId);
movieServiceImpl.removeMovieById(str);
return new ModelAndView("redirect:/");
}
五、特定功能单元
5.1 Axios
5.1.1 基本介绍
主要的两个注解
-
@RequestBody:方法形参
-
@ResponseBody:类或方法
下面的示例是用JSON格式进行传输
注意:异步请求的返回值参数好像不能用模型,如果用模型返回前端页面好像无法读取
5.1.2 示例代码
1、可以在形参上填写类对象,那么从前端传送过去的数据会根据set方法对这个形参对象进行相应的注入
2、也可以在码方法的时候,返回值选择对象而不是字符串,那么返回给页面的时候他也能自动toString
html
<head>
<script type="text/javascript" src="/04_SpringMVC/static/vue.js"></script>
<script type="text/javascript" src="/04_SpringMVC/static/axios.js"></script>
<meta charset="UTF-8">
<title>跳转页</title>
</head>
--------------------------------------
<div id="test">
<form th:action="@{/}">
请输入:<input type="text"
placeholder="请输入用户名"
name="userName"
v-model="userName"
@blur="blurFocus"
>
<button type="submit">提交</button>
</form>
</div>
javascript
let vue1 = new Vue(
{
el: "#test",
data: {
userName: ""
},
methods: {
blurFocus() {
axios({
method: "POST",
// Thymeleaf语法,加上项目前缀
url: "[[@{/asioxTest}]]",
data: {
userName: this.userName
}
// 这个data就是传json的意思了,以前穿普参用params
}
).then(function (resp) {
if (resp.data == "-1") {
alert("你有毛病吧")
} else if (resp.data == "1") {
alert("希望人没事")
} else {
console.info(resp.data)
}
}).catch(function (error) {
console.error(error)
})
}
}
}
)
java
@RestController
public class AxiosTest {
@RequestMapping(value = "/asioxTest")
public String asioxTest(@RequestBody String userName) {
System.out.println(userName + ":是你!!");
String x = "Oh.......No!";
return "1";
}
}
5.2 拦截器
5.2.1 概述
MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。
5.2.2 示例代码
要使用Spring MVC中的拦截器,就需要对拦截器类进行定义和配置。通常拦截器类可以通过两种方式来定义
1、通过实现 HandlerInterceptor 接口或者继承 HandlerInterceptor 接口的实现类(如HandlerInterceptorAdapter)来定义。
2、通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义。
以实现HandlerInterceptor接口的定义方式为例,自定义拦截器类的代码如下所示。
提示:在拦截器中,将数据存入请求域、转发或重定向请求都需要使用原生对象来完成,SpringMVC 并没有提供 Model、ModelMap 等 API 供我们使用。
spring-config.xml
<!-- 注册拦截器 -->
<mvc:interceptors>
<!-- 全局拦截器 -->
<bean class="Interceptor.Process01Interceptor"/>
<!-- 拦截器(1)精确匹配 -->
<mvc:interceptor>
<mvc:mapping path="/portal"/>
<bean class="Interceptor.Process01Interceptor"/>
</mvc:interceptor>
<!-- 拦截器(2)匹配单层路径 -->
<mvc:interceptor>
<mvc:mapping path="/portal/*"/>
<bean class="Interceptor.Process01Interceptor"/>
</mvc:interceptor>
<!-- 拦截器(3)匹配多层路径 -->
<mvc:interceptor>
<mvc:mapping path="/portal/**"/>
<bean class="Interceptor.Process01Interceptor"/>
</mvc:interceptor>
<!-- 拦截器(4)匹配不拦截路径 -->
<mvc:interceptor>
<mvc:mapping path="/portal/**"/>
<!-- 使用 mvc:exclude-mapping 标签配置不拦截的地址 -->
<mvc:exclude-mapping path="/portal/request/bbb"/>
<bean class="Interceptor.Process01Interceptor"/>
</mvc:interceptor>
</mvc:interceptors>
配置拦截器类
package 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 Process01Interceptor implements HandlerInterceptor {
/*
* preHandle()方法:
* 该方法会在控制器方法前执行,其返回值表示是否中断后续操作。
* 当其返回值为true时,表示继续向下执行;
* 当其返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)。
*
* postHandle()方法:
* 该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步的修改。
*
* afterCompletion()方法:
* 该方法在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些异常处理、资源清理、记录日志信息等工作。
* */
/*
* 当有多个拦截器同时工作时,它们的preHandle()方法会按照配置文件中拦截器的配置顺序执行
* 而它们的postHandle()方法和afterCompletion()方法则会按照配置顺序的反序执行
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle---");
/*
* 如果返回false,需要提供拦截后的跳转页面
* request.getRequestDispatcher("error").forward(request,response);
*
* 同时,在这个方法中可以获取请求 request 的参数进行判断是否放行
* */
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle---");
/*
* 在目标资源后执行,可以篡改跳转后的资源数据
* modelAndView.addObject("username","我被postHandle拐走了");
*
* 也可以篡改目标跳转走向
* modelAndView.setViewName("error");
* */
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion---");
}
}
5.3 类型转换
SpringMVC 将『把请求参数注入到 POJO 对象』这个操作称为『数据绑定』,SpringMVC 对基本数据类型提供了自动的类型转换。例如:请求参数传入“100”字符串,我们实体类中需要的是 Integer 类型,那么 SpringMVC 会自动将字符串转换为 Integer 类型注入实体类。
5.3.1 日期和事件类型格式绑定
通过注解设定格式
public class Product {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date productDate;
@NumberFormat(pattern = "###,###,###.###")
private Double productPrice;
表单
<form th:action="@{/save/product}" method="post">
生产日期:<input type="text" name="productDate" value="1992-10-15 17:15:06" /><br/>
产品价格:<input type="text" name="productPrice" value="111,222,333.444" /><br/>
<button type="submit">保存</button>
</form>
5.3.2 转换失败后的处理
BindingResult bindingResult对象可以获取到相关错误信息
- 如果不进行处理,默认会跳转到
400页面
BindingResult 接口和它的父接口 Errors 中定义了很多和数据绑定相关的方法,如果在数据绑定过程中发生了错误,那么通过这个接口类型的对象就可以获取到相关错误信息
分控制器方法
@RequestMapping("/save/product")
public String saveProduct(
Product product,
// 在实体类参数和 BindingResult 之间不能有任何其他参数
// 封装数据绑定结果的对象
BindingResult bindingResult) {
// 判断数据绑定过程中是否发生了错误
if (bindingResult.hasErrors()) {
// 如果发生了错误,则跳转到专门显示错误信息的页面
// 相关错误信息会自动被放到请求域
return "error";
}
logger.debug(product.toString());
return "target";
}
页面错误信息
<!-- th:errors 属性:用来显示请求处理过程中发生的错误 -->
<!-- th:errors 属性值:访问错误信息的表达式 -->
<!-- 访问错误信息的表达式:访问请求域,需要使用 ${} 格式 -->
<!-- 访问请求域使用的属性名:执行数据绑定的实体类的简单类名首字母小写 -->
<!-- 具体错误信息:找到实体类之后进一步访问出问题的属性名 -->
<p th:errors="${product.productDate}">这里显示具体错误信息</p>
5.3.3 自定义类型转换器
在实际开发过程中,难免会有某些情况需要使用自定义类型转换器。因为我们自己自定义的类型在 SpringMVC 中没有对应的内置类型转换器。此时需要我们提供自定义类型来执行转换
实体类
public class Student {
private Address address;
……
----------------------------------------------
public class Address {
private String province;
private String city;
private String street;
……
表单
<h3>自定义类型转换器</h3>
<form th:action="@{/save/student}" method="post">
地址:<input type="text" name="address" value="aaa,bbb,ccc" /><br/>
</form>
分控制器
@RequestMapping("/save/student")
public String saveStudent(Student student) {
logger.debug(student.getAddress().toString());
return "target";
}
创建自定义类型转换器类
public class AddressConverter implements Converter<String, Address> {
// String转Address
@Override
public Address convert(String source) {
// 1.按照约定的规则拆分源字符串
String[] split = source.split(",");
String province = split[0];
String city = split[1];
String street = split[2];
// 2.根据拆分结果创建 Address 对象
Address address = new Address(province, city, street);
// 3.返回转换得到的对象
return address;
}
}
SpringMVC 中注册
<!-- 在 mvc:annotation-driven 中注册 FormattingConversionServiceFactoryBean -->
<mvc:annotation-driven conversion-service="formattingConversionService"/>
<!-- 在 FormattingConversionServiceFactoryBean 中注册自定义类型转换器 -->
<bean id="formattingConversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!-- 在 converters 属性中指定自定义类型转换器 -->
<property name="converters">
<set>
<bean class="com.atguigu.mvc.converter.AddressConverter"/>
</set>
</property>
</bean>
5.4 数据校验
5.4.1 简述
表述层数据检查
JSR 303标准
| 注解 | 规则 |
|---|---|
| @Null | 标注值必须为 null |
| @NotNull | 标注值不可为 null |
| @AssertTrue | 标注值必须为 true |
| @AssertFalse | 标注值必须为 false |
| @Min(value) | 标注值必须大于或等于 value |
| @Max(value) | 标注值必须小于或等于 value |
| @DecimalMin(value) | 标注值必须大于或等于 value |
| @DecimalMax(value) | 标注值必须小于或等于 value |
| @Size(max,min) | 标注值大小必须在 max 和 min 限定的范围内 |
| @Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内 |
| @Past | 标注值只能用于日期型,且必须是过去的日期 |
| @Future | 标注值只能用于日期型,且必须是将来的日期 |
| @Pattern(value) | 标注值必须符合指定的正则表达式 |
其余扩展注解
| 注解 | 规则 |
|---|---|
| 标注值必须是格式正确的 Email 地址 | |
| @Length | 标注值字符串大小必须在指定的范围内 |
| @NotEmpty | 标注值字符串不能是空字符串 |
| @Range | 标注值必须在指定的范围内 |
5.4.2 示例代码
实体信息
// 字符串必须满足Email格式
@Email
private String email;
分控制器方法(注意注解@Validated)
@RequestMapping("/save/president")
public String savePresident(
// 在实体类参数和 BindingResult 之间不能有任何其他参数
@Validated President president, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "error";
}
logger.debug(president.getEmail());
return "target";
}
如果出现问题,则会在我们指定跳转的页面抛出400异常
<h1>系统信息</h1>
<!-- 从请求域获取实体类信息时,属性名是按照类名首字母小写的规则 -->
<!-- ${注入请求参数的实体类.出问题的字段} -->
<p th:errors="${president.email}">这里显示系统提示消息</p>
5.5 异常映射
5.5.1 概述
①微观
将异常类型和某个具体的视图关联起来,建立映射关系。好处是可以通过 SpringMVC 框架来帮助我们管理异常。
- 声明式管理异常:在配置文件中指定异常类型和视图之间的对应关系。在配置文件或注解类中统一管理。
- 编程式管理异常:需要我们自己手动 try ... catch ... 捕获异常,然后再手动跳转到某个页面。
②宏观
整个项目从架构这个层面设计的异常处理的统一机制和规范。
一个项目中会包含很多个模块,各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常,李四负责的模块按照 B 方案处理异常……各个模块处理异常的思路、代码、命名细节都不一样,那么就会让整个项目非常混乱
5.5.2 基于XML的异常映射
懒得看
5.5.3 基于注解的异常映射
异常处理器类加入IOC容器
<context:component-scan base-package="com.atguigu.mvc.exception"/>
声明处理异常的方法
// 异常处理器类需要使用 @ControllerAdvice 注解标记
@ControllerAdvice
public class MyExceptionHandler {
// @ExceptionHandler注解:标记异常处理方法
// value属性:指定匹配的异常类型,可以是RunTimeException
// 异常类型的形参:SpringMVC 捕获到的异常对象
@ExceptionHandler(value = NullPointerException.class)
public String resolveNullPointerException(Exception e, Model model) {
// 我们可以自己手动将异常对象存入模型
model.addAttribute("Exception", e);
// 返回逻辑视图名称,在页面就可以利用Thymeleaf显示异常信息
return "error-nullpointer";
}
}
5.5.4 区分同步/异步请求
查看请求消息头中是否包含 Ajax 请求独有的特征:
- Accept 请求消息头:包含 application/json
所以可以写一个判断来辨别
Boolean bo = request.getHeader("Accept").contains("application/json");
System.out.println("他是JSON吗" + bo);
5.6 文件上传
5.6.1 依赖
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
5.6.2 mvc配置文件
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 由于上传文件的表单请求体编码方式是 multipart/form-data 格式,所以要在解析器中指定字符集 -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>
5.6.3 逻辑组成
5.6.4 示例代码
import java.io.File;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletContext;
import java.io.IOException;
@Controller
public class UploadDemo01 {
@Autowired
private ServletContext servletContext;
@RequestMapping(value = "/simple/upload", method = {RequestMethod.POST})
public ModelAndView upload(
@RequestParam(value = "pic") MultipartFile pic
) throws IOException, NoSuchAlgorithmException {
// 1、虚拟路径 - 需求根因:解决多操作系统下的路径问题
String destFileFolderVirtualPath = "/DownloadDemo01";
// 2、调用 ServletContext 对象的方法将虚拟路径转换为真实物理路径
String destFileFolderRealPath = servletContext.getRealPath(destFileFolderVirtualPath);
// 判断文件是否为空
pic.isEmpty();
// 文件大小(有缺陷,已经上传到了服务器的临时文件夹)
pic.getSize();
// 当前上传文件的二进制内容组成的字节数组
pic.getBytes();
//使用UUID随机产生文件名称,防止同名文件覆盖
String fileName = UUID.randomUUID().toString() + "文件名后面带后缀的那个";
// 3、获取文件完整名称
String originalFilename = pic.getOriginalFilename();
// 4、根据文件完整名称获取文件扩展名
String fileExtname = originalFilename.substring(originalFilename.lastIndexOf("."));
// 5、获取MD5转换器
MessageDigest md5 = MessageDigest.getInstance("MD5");
// 6、获取文件的byte信息
byte[] uploadBytes = pic.getBytes();
// 7、对文件进行MD5加密
byte[] digest = md5.digest(uploadBytes);
// 8、转换为16进制
String fileFirstname = new BigInteger(1, digest).toString(16);
// 9、拼装起来就是我们生成的整体文件名
String destFileName = fileFirstname + "" + fileExtname;
// 10、路径 + 文件名 的拼接
String destFilePath = destFileFolderRealPath + "/" + destFileName;
// 11、数据转入File对象
File file = new File(destFilePath);
// 12、写入硬盘
pic.transferTo(file);
return new ModelAndView("redirect:/portal");
}
}
额外:关于文件上传大小控制解决方案
<!-- id 固定为multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"></property>
<!--一次上传的多个文件的总和的最大大小 -->
<property name="maxUploadSize" value="1000000"></property>
<!--上传的每个文件的最大大小 -->
<property name="maxUploadSizePerFile" value="100000"></property>
</bean>
5.7 文件下载
@Autowired
private ServletContext servletContext;
@RequestMapping("/download/file")
public ResponseEntity<byte[]> downloadFile() {
// 1.获取要下载的文件的输入流对象
// 这里指定的路径以 Web 应用根目录为基准
InputStream inputStream = servletContext.getResourceAsStream("/images/mi.jpg");
try {
// 2.将要下载的文件读取到字节数组中
// ①获取目标文件的长度
int len = inputStream.available();
// ②根据目标文件长度创建字节数组
byte[] buffer = new byte[len];
// ③将目标文件读取到字节数组中
inputStream.read(buffer);
// 3.封装响应消息头
// ①创建MultiValueMap接口类型的对象,实现类是HttpHeaders
MultiValueMap responseHeaderMap = new HttpHeaders();
// ②存入下载文件所需要的响应消息头
responseHeaderMap.add("Content-Disposition", "attachment; filename=mi.jpg");
// ③创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(buffer, responseHeaderMap, HttpStatus.OK);
// 4.返回responseEntity对象
return responseEntity;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
五、原理解析篇
5.1 MVC与用户请求的交互过程
5.2 MVC的核心API
-
DispatcherServlet:
前端总控制器 获取前端请求并与后端模块进行交互
-
HandlerMapping:
处理映射器 建立了请求路径和分控制器方法之间的映射
-
HandlerExecutionChain:
处理器执行链 总控制器调用HandlerMapping组件的返回值是一个执行链,不仅有要执行的分控制器方法,还有相应的多个拦截器,组成一个执行链。
此类下包含了分控制器方法和列表类型的拦截器,所以它是按顺序链式执行的
-
HandlerAdapter:
处理适配器 调用分控制器的方法,不是由总控制器之间调用的,而是由HandlerAdapter来调用
因为会有XML方式、注解方式等处理器形式,具体执行会有不同,通过不同的HandlerAdapter来实现。这里用到了适配器设计模式
-
ViewResolver:
视图解析器 将前端控制器交付的视图进行解析
逻辑视图(result)----->物理视图(/WEB-INF/templates/result.html)
5.3 ContextLoaderListener解析
引入简介
SSM整合后,配置文件内容过多,可以分到两个配置文件中。这两个配置文件夹如何加载
方法1:DispatcherServlet加载所有的配置文件(只有一个Ioc容器,存放所有的Bean)
<!--配置SpringMVC总控制器,唯一的Servlet -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--指定SpringMVC配置文件的名称和位置,有默认位置 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring*.xml</param-value>
</init-param>
<!-- 启动服务器时就加载总控制器-->
<load-on-startup>1</load-on-startup>
</servlet>
方法2:DispatcherServlet加载springmvc的配置文件,使用ContextLoaderListener加载另外一个配置文件(会有两个Ioc容器)
会有两个Ioc容器
使用ContextLoaderListener加载另外一个配置文件创建的IoC容器是父容器。
DispatcherServlet加载springmvc的配置文件创建的IoC容器是子容器。
注意:Servlet、Filter、Listener的加载顺序:Listener、Filter、Servlet
我们一般都会在MVC中的web.xml进行如下配置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
总结
- 它继承
ContextLoader类
ContextLoaderListener可以指定在Web应用程序启动时载入Ioc容器
- 也实现
ServletContextListener接口
用来监听Servlet容器的初始化,当tomcat启动时会初始化一个Servlet容器,这样ContextLoaderListener会监听到Servlet容器的初始化,这样在Servlet容器初始化之后我们就可以在ContextLoaderListener中也进行一些初始化操作
总结
在启动Web容器时(继承了
ContextLoader类,可以指定在Web应用程序启动时载入Ioc容器),开始初始化spring的web应用上下文(因为实现了ServletContextListener接口,启动容器时,就会默认执行它实现的方法),读取ContextConfigLocation中定义的xml文件,自动装配ApplicationContext的配置信息并产生WebApplicationContext对象,然后将这个对象放置在ServletContext的属性里。这样我们只要得到Servlet就可以得到WebApplicationContext对象,并利用这个对象访问spring容器管理的bean
一句话概括:为项目提供了spring支持,初始化了Ioc容器
问题挖掘
如果<context:component-scan >的路径设置不合理,就会重复的创建Bean。 如何查看:将logback的总的日志级别改为DEBUG
缺点:
-
重复的bean会多占用资源
-
SpringMVC创建的Controller肯定是调用SpringMVC自己创建的Service和Dao,但是在SpringMVC的配置文件中并没有关于事务的设置,所以调用SpringMVC自己创建的Service和Dao,将无法使用到事务。这绝对不可以。
-
解决方案1:两个配置文件中扫描不同的包
<!-- 配置注解扫描基准路径--> <context:component-scan base-package="com.atguigu.service,com.atguigu.dao"></context:component-scan> <!-- 配置注解扫描基准路径--> <context:component-scan base-package="com.atguigu.controller"></context:component-scan>结果:SpringMVC中创建了Controller,Listener中创建了Service并应用了事务。当SpringMVC在自己的IoC容器中找不到Service的时候,就会到父容器中去找Service。问题解决。
-
解决方案2:两个配置文件中扫描不同的包
<!-- 配置注解扫描基准路径: use-default-filters="true" @Controller @Service @Repository @Component use-default-filters="false" 只扫描基准包下被include-filter指定的注解 --> <context:component-scan base-package="com.atguigu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 配置注解扫描基准路径:@Controller @Service @Repository @Component--> <context:component-scan base-package="com.atguigu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
5.4 DispatcherServlet的请求处理过程
整个请求处理过程都是 DispatcherServlet 的 doDispatch() 方法在宏观上协调和调度
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 检查请求是否是multipart(即文件上传),若是进行相关处理
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 确定当前请求的处理程序
mappedHandler = getHandler(processedRequest);
// 如果找不到对应的处理器的话,就像这样
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 确定当前请求的处理程序适配器(例如是注解适配器还是配置文件适配器)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 如果处理程序支持,则处理最后修改的头文件
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 执行chain中拦截器附加的预处理方法,即preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 适配器统一执行handle方法(适配器统一接口的作用),此处是真正处理业务逻辑的地方
// 执行分控制器方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理分发结果:包括解析视图并进行视图渲染,执行chain中拦截器附加的后处理方法,即afterCompletion方法
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
具体看源码吧
5.5 客户端访问控制层的基础交互
简单囊括
服务器解析客户端的URL请求获取Controller对象和对应的接口方法,然后运用Java的反射运行对应的接口方法。
不简单囊括
获取的方式依赖@Controller和@RequestMapping注解,由URL解析出接口方法上的注解@RequestMapping的值,再根据这个值映射对应的接口方法。
这种映射方式需要把注解@RequestMapping的值放入Map容器中缓存起来,Map中的key为注解@RequestMapping的值、value为对应的接口方法的Method对象。当读取URL时,就相当于有了key,此时就可以从容器中获取接口方法的Method对象了。
5.6 拦截器与过滤器的区别
| 类型 | 过滤器Filter | 拦截器interceptor |
|---|---|---|
| 规范 | Filter是在Servlet规范中定义的,是Servlet容器支持的 | 拦截器是在Spring容器内的,是Spring框架支持的 |
| 使用范围 | 过滤器只能用于Web程序中 | 拦截器既可以用于Web程序,也可以用于Application、Swing程序中 |
| 原理 | 过滤器是基于函数回调 | 拦截器是基于java的反射机制 |
| 使用的资源 | 过滤器不能使用Spring资源 | 拦截器是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如Service对象、数据源、事务管理等,可以通过loC注入到拦截器 |
| 深度 | Filter在只在Servlet前后起作用 | 拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性 |