概述
新建Maven Web项目
版本4.0,以jsp演示
Maven引入
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency><!--Spring核心依赖-->
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.3</version>
</dependency>
<dependency><!--servlet3.0以上才有注解-->
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency><!--jstl包含servlet2.5 jsp2.1-->
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<!-- 防止资源导出错误 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
HelloSpringMVC
SpringMVC配置文件
<?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">
<!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
<context:component-scan base-package="com.demo.controller"/>
<!-- 让Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler/>
<!-- 支持mvc注解驱动
在spring中一般采用@RequestMapping注解来完成映射关系
要想使@RequestMapping注解生效 必须向上下文中注册DefaultAnnotationHandlerMapping
和一个AnnotationMethodHandlerAdapter实例 这两个实例分别在类级别和方法级别处理。
而annotation-driven配置帮助我们自动完成上述两个实例的注入。 -->
<mvc:annotation-driven/>
<!--视图解析器:DispatcherServlet给他的ModelAndView-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/> <!--前缀-->
<property name="suffix" value=".jsp"/> <!--后缀-->
</bean>
</beans>
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"
metadata-complete="false">
<!--注册DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!--关联一个springmvc的配置文件-->
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!--启动级别-1 数字越小,启动越早-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--所有请求都会被springmvc拦截 -->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--/ 匹配所有的请求:(不包括.jsp) /* 匹配所有的请求:(包括.jsp)-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
视图层JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${msg}
</body>
</html>
Controller层
@Controller
@RequestMapping("/HelloController")
public class HelloController {
// 真实访问地址 : 项目名/HelloController/hello
@RequestMapping("/hello")
public String sayHello(Model model) { // 方法中声明Model类型的参数是为了把Action中的数据带到视图中
// 向模型中添加属性msg与值,可以在JSP页面中取出并渲染
model.addAttribute("msg", "Hello,SpringMVC");
// 会被视图解析器处理 /WEB-INF/jsp/hello.jsp
return "hello";
}
}
测试
RestFul风格
组合注解
@RequestMapping 默认都可以接收,也可以限定接收方法
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
@PathVariable
/**
* 原来:http://localhost:8080/add1?a=1&b=2
*/
@RequestMapping("/add1")
public String add1(int a, int b, Model model) {
model.addAttribute("result", a + b);
return "test";
}
/**
* 可以使用 @PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上
* RestFul:http://localhost:8080/add/1/2
*/
// @RequestMapping(value = "/add/{a}/{b}", method = RequestMethod.GET)
@GetMapping("/add/{a}/{b}")
public String add(@PathVariable int a, @PathVariable int b, Model model) {
model.addAttribute("result", a + b);
return "test";
}
结果跳转方式
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${result}
</body>
</html>
ModelAndView
public class ControllerTest1 implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
//返回一个模型视图对象
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "ControllerTest1");
mv.setViewName("test");
return mv;
}
}
ServletAPI
通过设置ServletAPI , 无视视图解析器
@RequestMapping("/r1")
public void test1(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 重定向
request.getSession().setAttribute("result", "r1");
response.sendRedirect(request.getContextPath() + "/test.jsp");
}
@RequestMapping("/r2")
public void test2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 请求转发
request.setAttribute("result", "r2");
request.getRequestDispatcher("/test.jsp").forward(request, response);
}
SpringMVC
通过SpringMVC来实现转发和重定向 - 注释视图解析器
@RequestMapping("/r3")
public String test3() {
// 请求转发
return "/index.jsp";
}
@RequestMapping("/r4")
public String test4(HttpServletRequest request) {
// 请求转发
request.setAttribute("result", "r4");
return "forward:/test.jsp";
}
@RequestMapping("/r5")
public String test5(HttpServletRequest request, Model model) {
// 没有视图解析器不能通过Model得到数据
model.addAttribute("result", "r55");
// 也不能通过这种方式传值
request.setAttribute("result", "r5");
// 重定向,会自动加上项目名
return "redirect:/test.jsp";
}
有视图解析器
重定向可以重定向到另一个请求
@RequestMapping("/r6")
public String test6() {
return "redirect:/hello";
}
数据处理
处理提交数据
可以接受get
和post
表单方式传参
提交的域名称和处理方法的参数名一致
提交数据 :
http://localhost:8081/web/data1?name=张三
处理方法:
@RequestMapping("/data1")
public String test1(String name) {
System.out.println(name); // 张三
return "hello";
}
提交的域名称和处理方法的参数名不一致
提交数据 :
http://localhost:8081/web/data2?username=张三
处理方法:
@RequestMapping("/data2")
// required属性可以控制是否一定要传参,没有则为null,建议所有类型都为引用类型
public String test2(@RequestParam(value = "username", required = false) String name) {
System.out.println(name); // 张三
return "hello";
}
最好都加上,区分前端传参,更规范
提交的是一个对象
实体类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
}
提交数据 :
http://localhost:8081/web/data3?id=1&name=张三
处理方法:
@RequestMapping("/data3")
public String test3(User user) {
System.out.println(user); // User(id=1, name=张三)
return "hello";
}
数据提交到前端
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${requestScope.msg}
</body>
</html>
通过ModelAndView
public class ControllerTest1 implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 返回一个模型视图对象
ModelAndView mv = new ModelAndView();
// 数据在request域中
mv.addObject("msg", "ControllerTest1");
mv.setViewName("test");
return mv;
}
}
通过ModelMap
@RequestMapping("/d4")
public String test4(ModelMap modelMap) {
modelMap.addAttribute("msg", "d4");
return "hello";
}
通过Model
@RequestMapping("/d5")
public String test5(Model model) {
model.addAttribute("msg", "d5");
return "hello";
}
对比
- Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解;
- ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性;
- ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转。
乱码问题
SpringMVC给我们提供了一个过滤器 , 可以在web.xml中配置
一般情况下,SpringMVC默认的乱码处理就已经能够很好的解决了
修改了xml文件需要重启服务器
<filter>
<filter-name>encoding</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>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
极端情况下.这个过滤器对get的支持不好
处理方法 :
- 修改tomcat配置文件 : 设置编码
<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
- 自定义过滤器
public class GenericEncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//处理response的字符编码
HttpServletResponse myResponse = (HttpServletResponse) response;
myResponse.setContentType("text/html;charset=utf-8");
// 转型为与协议相关对象
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 对request包装增强
HttpServletRequest myrequest = new MyRequest(httpServletRequest);
chain.doFilter(myrequest, response);
}
//自定义request对象,HttpServletRequest的包装类
static class MyRequest extends HttpServletRequestWrapper {
private final HttpServletRequest request;
// 是否编码的标记
private boolean hasEncode;
// 定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
public MyRequest(HttpServletRequest request) {
super(request); // super必须写
this.request = request;
}
// 对需要增强方法 进行覆盖
@Override
public Map<String, String[]> getParameterMap() {
// 先获得请求方式
String method = request.getMethod();
if (method.equalsIgnoreCase("post")) {
// post请求
try {
// 处理post乱码
request.setCharacterEncoding("utf-8");
return request.getParameterMap();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else if (method.equalsIgnoreCase("get")) {
// get请求
Map<String, String[]> parameterMap = request.getParameterMap();
if (!hasEncode) { // 确保get手动编码逻辑只运行一次
for (String parameterName : parameterMap.keySet()) {
String[] values = parameterMap.get(parameterName);
if (values != null) {
for (int i = 0; i < values.length; i++) {
// 处理get乱码
values[i] = new String(values[i].getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
}
}
}
hasEncode = true;
}
return super.getParameterMap();
}
return super.getParameterMap();
}
// 取一个值
@Override
public String getParameter(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
if (values == null) {
return null;
}
return values[0];
// 取回参数的第一个值
}
// 取所有值
@Override
public String[] getParameterValues(String name) {
Map<String, String[]> parameterMap = getParameterMap();
return parameterMap.get(name);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
在web.xml中配置这个过滤器即可
Json
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
Controller返回Json数据
// produces解决json乱码问题,指定响应体返回类型和编码
@RequestMapping(value = "/j1", produces = "application/json;charset=utf-8")
@ResponseBody // 不会走视图解析器,直接返回字符串
public String test1() throws JsonProcessingException {
// 创建一个jackson对象映射器,用来解析数据
ObjectMapper mapper = new ObjectMapper();
// 创建一个对象
User user1 = new User(1, "张三1");
User user2 = new User(2, "张三2");
User user3 = new User(3, "张三3");
List<User> userList = new ArrayList<>();
userList.add(user1);
userList.add(user2);
userList.add(user3);
// 将对象转为json字符串
String userJsonStr = mapper.writeValueAsString(userList);
return userJsonStr;
}
统一解决乱码
Springmvc配置文件中新增:(需要jackson包)
<!--json字符串乱码问题解决-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
返回的一定要是标准json格式,属性名要用双引号,属性值如果是字符串也要用双引号
@RestController
在类上直接使用 @RestController
,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加 @ResponseBody
@RestController=@Controller+@ResponseBody
FastJson
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
@RequestMapping(value = "/j2")
@ResponseBody
public String test2() throws JsonProcessingException {
// 创建一个对象
User user1 = new User(1, "张三1");
User user2 = new User(2, "张三2");
User user3 = new User(3, "张三3");
List<User> userList = new ArrayList<>();
userList.add(user1);
userList.add(user2);
userList.add(user3);
System.out.println("*******Java对象 转 JSON字符串*******");
String str1 = JSON.toJSONString(userList);
System.out.println("JSON.toJSONString(list) ==> " + str1);
String str2 = JSON.toJSONString(user1);
System.out.println("JSON.toJSONString(user1) ==> " + str2);
System.out.println("
****** JSON字符串 转 Java对象*******");
User jp_user1 = JSON.parseObject(str2, User.class);
System.out.println("JSON.parseObject(str2,User.class) ==> " + jp_user1);
System.out.println("
****** Java对象 转 JSON对象 ******");
JSONObject jsonObject1 = (JSONObject) JSON.toJSON(user2);
System.out.println("(JSONObject) JSON.toJSON(user2) ==> " + jsonObject1.getString("name"));
System.out.println("
****** JSON对象 转 Java对象 ******");
User to_java_user = JSON.toJavaObject(jsonObject1, User.class);
System.out.println("JSON.toJavaObject(jsonObject1, User.class) ==> " + to_java_user);
return JSON.toJSONString(userList);
}
拦截器
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能,拦截器是AOP思想的具体应用
过滤器
- servlet规范中的一部分,任何java web工程都可以使用
- 在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截
拦截器
- 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
- 拦截器只会拦截访问的控制器方法, 如果访问的是jsp/html/css/image/js是不会进行拦截的
自定义拦截器
Interceptor
public class MyInterceptor implements HandlerInterceptor {
// 在请求处理的方法之前执行
// 如果返回true执行下一个拦截器
// 如果返回false就不执行下一个拦截器
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("------------处理前------------");
if (request.getRequestURI().toLowerCase().contains("login")) {
// 登陆链接放行
if (request.getSession().getAttribute("username") != null) {
// 已登陆跳转到主页
request.getRequestDispatcher("/WEB-INF/jsp/main.jsp").forward(request, response);
} else {
return true;
}
} else {
// 其他连接判断是否登陆
if (request.getSession().getAttribute("username") == null) {
// 没有登陆跳到登陆连接
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
} else {
return true;
}
}
return false;
}
// 在请求处理方法执行之后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// System.out.println("------------处理后------------");
}
//在dispatcherServlet处理后执行,做清理工作
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// System.out.println("------------清理------------");
}
}
spring-mvc.xml
<!--拦截器配置-->
<mvc:interceptors>
<mvc:interceptor>
<!--包括这个请求下的所有请求-->
<mvc:mapping path="/**"/>
<bean class="com.demo.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
index.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<h3>
<a href="${pageContext.request.contextPath}/user/toLoginPage">登陆</a>
</h3>
<c:if test="${sessionScope.username.equals('admin')}">
<h3>
<a href="${pageContext.request.contextPath}/user/logout">退出登陆</a>
</h3>
</c:if>
<h3>
<a href="${pageContext.request.contextPath}/user/toMainPage">主页</a>
</h3>
<span style="color: orange">${sessionScope.msg}</span>
${pageContext.session.removeAttribute("msg")}
</body>
</html>
main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>主页</title>
</head>
<body>
<h1>主页</h1>
<a href="${pageContext.request.contextPath}/index.jsp">首页</a>
</body>
</html>
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登陆</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/user/login" method="post">
<p><input type="text" name="username"></p>
<p><input type="text" name="password"></p>
<p><input type="submit" value="登陆"></p>
</form>
<a href="${pageContext.request.contextPath}/index.jsp">首页</a>
</body>
</html>
文件上传和下载
文件上传
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
springmvc.xml
<!--文件上传配置-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容, 默认为ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
upload.jsp
<form action="${pageContext.request.contextPath}/upload2" enctype="multipart/form-data" method="post">
<input type="file" name="file"> <br/>
<input type="submit" value="上传">
</form>
Controller
// @RequestParam("file") 将name=file控件得到的文件封装成 CommonsMultipartFile 对象
// 批量上传CommonsMultipartFile则为数组即可
@RequestMapping("/upload")
public String upload(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//获取文件名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();
//如果文件名为空
if ("".equals(uploadFileName)) {
return "上传失败";
}
System.out.println("上传文件名 : " + uploadFileName);
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()) {
realPath.mkdir();
}
System.out.println("上传文件保存地址:" + realPath);
InputStream is = file.getInputStream(); //文件输入流
OutputStream os = new FileOutputStream(new File(realPath, uploadFileName)); //文件输出流
//读取写出
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
os.flush();
}
os.close();
is.close();
return "上传成功";
}
// 采用file.Transto 来保存上传的文件
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
// 上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()) {
realPath.mkdir();
}
System.out.println("上传文件保存地址:" + realPath);
System.out.println("上传文件名 : " + file.getOriginalFilename());
// 通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(new File(realPath + "/" + file.getOriginalFilename()));
return "上传成功";
}
文件下载
download.jsp
<a href="${pageContext.request.contextPath}/download">点击下载</a>
Controller
@RequestMapping(value = "/download")
public String downloads(HttpServletResponse response, HttpServletRequest request) throws Exception {
// 要下载的图片地址
String path = request.getServletContext().getRealPath("/upload");
String fileName = "动漫头像.JPG";
// 1、设置response 响应头
response.reset(); // 设置页面不缓存,清空buffer
response.setCharacterEncoding("UTF-8"); // 字符编码
response.setContentType("multipart/form-data"); // 二进制传输数据
response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, "UTF-8").replace("+", "%20"));
File file = new File(path, fileName);
// 2、 读取文件--输入流
InputStream input = new FileInputStream(file);
// 3、 写出文件--输出流
OutputStream out = response.getOutputStream();
byte[] buff = new byte[1024];
int index = 0;
// 4、执行 写出操作
while ((index = input.read(buff)) != -1) {
out.write(buff, 0, index);
out.flush();
}
out.close();
input.close();
return "下载成功";
}