文章目录
SpringMVC简介
SpringMVC是基于java的控制层(controller控制器)Web框架,属于Spring FrameWork的后续产品。
- 总体SSM结构图
- SpringMVC执行流程图
常用注解
处理器注解(controller)
@RequestMapping的属性
value:用于指定请求的 URL。它和 path 属性的作用是一样的。
method:用于指定请求的方式。
params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的 key 和 value 必须和
配置的一模一样。
例如:
params = {"accountName"},表示请求参数必须有 accountName
params = {"moeny!100"},表示请求参数中 money 不能是 100。
headers:用于指定限制请求消息头的条件。
注意:
以上四个属性只要出现 2 个或以上时,他们的关系是与的关系。
@Controller
//标识一级目录可以不写
@RequestMapping("/helloM")
public class HelloMVC {
//path或value表示二级目录或一级目录(类注解没写时)
//表示限制的属性:
// method = {RequestMethod.GET} 只能get请求才能访问此方法
// headers = {"Accept"} 请求必须携带Accept这个请求头
// params = {"username"} 请求必须携带username Key信息
// params = {"username=tdte"} 请求必须携带"username" Key信息和"tdte" Value信息
@RequestMapping(path = "/hello",method = {RequestMethod.GET},headers = {""},params = {"username"})
public String pringHello() {
System.out.println("hello mvc");
return "success";
}
}
请求参数绑定
简单类型
//springmvc可以根据请求的参数的key值匹配参数
//如前端传过来参数username=tdte
//则下面打印 用户名:tdte
@RequestMapping(params = "/param")
public String testParam(String username, String password) {
System.out.println("testParam.....");
System.out.println("用户名:" + username);
System.out.println("密码:" + password);
return "success";
}
实体类型的自动参数绑定
//spring 会根据前端参数的key值自动绑定pojo类的set属性
@RequestMapping(path = "/user")
public String testPojo(User user) {
System.out.println("testParam.....");
System.out.println(user);
return "success";
}
测试用实体类:
import lombok.Data;
@Data
public class Account {
private String aname;
private String apwd;
}
@Data
public class User implements Serializable {
private String uname;
private String upwd;
private Double umoney;
//引用Account类
private Account account;
}
前端测试表单:
<!--注意 post表单如果提交到后台会出现中文乱码问题-->
<form action="helloM/user" method="post">
用户名称:<input type="text" name="uname"><br/>
密码:<input type="text" name="upwd"><br/>
金额:<input type="text" name="umoney"><br/>
<!-- 注意此处 引用类型用.进行深层赋值 -->
账户名称:<input type="text" name="account.aname"><br/>
账户密码:<input type="text" name="account.apwd"><br/>
<input type="submit" value="提交"><br/>
</form>
复杂类型自动参数绑定(集合)
- 使用到了类:
测试pojo:
@Data
public class User implements Serializable {
private String uname;
private String upwd;
private Double umoney;
private List<Account> list;
private Map<String,Account> map;
}
@Data
public class Account {
private String aname;
private String apwd;
}
测试处理器:
//spring 会根据前端参数的key值自动绑定pojo类的set属性
@RequestMapping(path = "/user")
public String testPojo(User user) {
System.out.println("testParam.....");
System.out.println(user);
return "success";
}
前端表单表达式:
<form action="helloM/user" method="post">
用户名称:<input type="text" name="uname"><br/>
密码:<input type="text" name="upwd"><br/>
金额:<input type="text" name="umoney"><br/>
<!--注意此处表达式和上面的pojo属性的匹配-->
账户名称:<input type="text" name="List[0].aname"><br/>
账户密码:<input type="text" name="list[0].apwd"><br/>
<!--注意:map的key需要手动写入或者前端js-->
账户名称:<input type="text" name="map['tdte'].aname"><br/>
账户密码:<input type="text" name="map['tdte'].apwd"><br/>
<input type="submit" value="提交"><br/>
</form>
自定义类型转换
我们提交数据的时候,request中的数据都是以String的类型存在的,Spring会做一些类型转换,将这些数据转换成我们所需要的数据类型(int、float等)。对于日期来说,Spring支持的格式是2020/01/10,当我们传入2020-01-10,程序会报错,这时候就需要我们自定义类型转换器来满足我们的需要。
测试用pojo类:
@Data
public class Account {
private String aname;
private String apwd;
private Date date;
}
定义转换类,实现Converter接口,在重写方法中对业务进行实现:
public class StringToDateConverter implements Converter<String, Date> {
/**
* 自定义类型转换器,手动把字符串转换成Date类型
*
* @param source 被转换的字符串
* @return 转换后的字符串
*/
@Override
public Date convert(String source) {
if ("".equals(source) || source == null) {
throw new RuntimeException("参数为空");
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
//字符串转换
return df.parse(source);
} catch (ParseException e) {
throw new RuntimeException("字符串解析异常");
}
}
}
配置spring的xml文件:
<!-- 注册自定义类型转换器 -->
<bean id="stringToDateConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="top.tdte.util.StringToDateConverter"/>
</set>
</property>
</bean>
<!--在注解驱动中添加属性 conversion-service-->
<mvc:annotation-driven conversion-service="stringToDateConversionService"/>
测试方法:
//spring 会根据前端参数的key值自动绑定pojo类的set属性
@RequestMapping(path = "/account")
public String testConverter(Account account) {
System.out.println("testConverter.....");
System.out.println(account);
return "success";
}
配置springmvc过滤器解决中文乱码问题
在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>
常用参数注解
RequestParam注解:
@Controller
@RequestMapping("/comm")
public class commonAnnotation {
@RequestMapping("/test")
// @RequestParam 表示前端传入的参数可以不必须匹配方法的参数名称
// 可以用name或value属性进行映射,
// required 属性默认为true表示前端name表单属性必须与name匹配,否则404
public String test(@RequestParam(name = "name") String username) {
System.out.println("方法执行");
return "success";
}
}
@RequestBody:
//获得请求体 就是?后面的全部键值
//注意参数不能与前端表单name属性重名,否则优先使用同名匹配
@RequestMapping("/test1")
public String test1(@RequestBody String body) {
System.out.println("方法执行");
System.out.println(body);
return "success";
}
@PathVaribale:
//根据rest风格匹配具体请求的方法
//rest风格是根据同一个url不同请求方式进行区分的,或根据{}占位符的值进行区分
//下面是根据占位符进行区分,并且获取占位符参数
//前端传入的url可以为comm/test1/10定位要执行的方法
@RequestMapping("/test1/{sid}")
public String test2(@PathVariable("sid") String id) {
System.out.println("方法执行....");
System.out.println(id);
return "success";
}
RequestHeader注解:
//获取头信息 Accept头的值
@RequestMapping(path="/hello")
public String sayHello(@RequestHeader(value="Accept") String header) {
System.out.println(header);
return "success";
}
CookieValue
@RequestMapping(path="/hello")
public String sayHello(@CookieValue(value="JSESSIONID") String cookieValue) {
System.out.println(cookieValue);
return "success";
}
ModelAttribute:
- 有返回值:
注意:前端必须没有这个省略的pojo实体类属性的表单,否则会出现在自动参数绑定上会出现异常。
@RequestMapping("/test3")
public String test3(Account account) {
System.out.println("方法执行....");
System.out.println(account);
return "success";
}
//如果请求本类的方法,这个方法会先执行,这个注解一般用于附初始值。
//有返回值值被ModelAttribute修饰的方法
@ModelAttribute
public Account testModelAttribute(String aname) {
System.out.println("ModelAtteribute.....");
Account ac = new Account();
ac.setAname(aname);
ac.setApwd("1234");
ac.setDate(new Date());
return ac;
}
- 无返回值:
// 参数注解用于取出map中的匹配的value值
@RequestMapping("/test3")
public String test3(@ModelAttribute("acc") Account account) {
System.out.println("方法执行....");
System.out.println(account);
return "success";
}
// 添加一个参数map其K代表一个id号,用于其被引用方法的关联,如上面方法用于修饰Account的参数的注解,
// V用来存储初始化的pojo对象
@ModelAttribute
public void testModelAttribute(String aname, Map<String,Account> map) {
System.out.println("ModelAtteribute.....");
Account ac = new Account();
ac.setAname(aname);
ac.setApwd("1234");
ac.setDate(new Date());
map.put("acc",ac);
}
SessionAttributes:
@Controller
@RequestMapping("/comm")
//把model中的域对象key=smg的值从request域对象拷贝到session中
@SessionAttributes("msg")
public class commonAnnotation {
@RequestMapping("/test4")
public String test4(Model model) {
System.out.println("方法执行....");
//model底层会存入request域对象中,前端jsp表达式就可以取出
model.addAttribute("msg", "消息测试");
return "success";
}
@RequestMapping("/test5")
public String test5(ModelMap modelMap, SessionStatus sessionStatus) {
System.out.println("方法执行....");
//取出session域对象的值
String msg = (String) modelMap.getAttribute("msg");
System.out.println(msg);
//删除session域对象
sessionStatus.setComplete();
return "success";
}
}
响应数据和结果视图
方法返回值跳转
视图解析器:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
根据返回值配合视图解析器跳转:
@RequestMapping("/hello")
public String hello() {
System.out.println("hello......");
return "success";
}
void返回值跳转
无返回值时springMVC会根据视图解析器跳转到/WEB-INF/jsp/Mtest/hello1.jsp
页面(根据RequestMapping值查找文件位置,找不到为404)此方法繁琐,一般不用此方法跳转。
@Controller
@RequestMapping("/Mtest")
public class TestController {
//跳转测试
@RequestMapping("/hello1")
public void hello1() {
System.out.println("hello1......");
}
}
使用传统ServletAPI跳转:
/**
* 是void 不使用视图解析器
* 请求转发一次请求,不用编写项目的名称
*/
@RequestMapping("/testVoid")
public void testVoid(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("testVoid方法执行了...");
// 编写请求转发的程序
// request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
// 重定向
// response.sendRedirect(request.getContextPath()+"/index.jsp");
// 设置中文乱码
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 直接会进行响应
response.getWriter().print("你好");
return;
}
重定向和转发的区别:
转发是一次请求,不需要携带项目名称如:
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
重定向是重新发了一次请求,需要携带项目名称:
response.sendRedirect(request.getContextPath()+"/index.jsp");
使用ModelAndView对象
实际上,返回字符串进行跳转,底层会使用到ModelAndView对象。
@RequestMapping("/test3")
public ModelAndView test3(User user) {
System.out.println("test3......");
//创建对象
ModelAndView modelAndView = new ModelAndView();
//把user对象存储到modelAndView对象中,也会把user对象存入到request对象,key=msg
modelAndView.addObject("msg",user);
//跳转到哪个页面
modelAndView.setViewName("success");
return modelAndView;
}
框架中的转发与重定向关键字:forward redirect
@RequestMapping("/test4")
public String test4(User user) {
System.out.println("test4......");
//使用forward关键字进行 请求转发
return "forward:/WEB-INF/jsp/success.jsp";
//注意:网站不能直接请求或重定向获取web-inf下的资源,但请求转发能获取,是因为转发没有建立新的请求。
// 在springmvc中使用redirect关键字进行中定向可以省略项目名称,底层会加上ContextPath。
return "redirect:springmvc02/WEB-INF/jsp/success.jsp";
}
静态资源被DispatcherServlet(前端控制器)拦截问题
问题描述
在web文件夹下有一个js/jquery.min.js文件。并且前端引用这个资源。由于前端是文件路径引用的,因此会被DispatcherServlet拦截调。
<script src="js/jquery.min.js"></script>
<script>
$(function () {
$("#btn").click(function () {
alert("弹窗测试");
});
});
</script>
解决方法
在springmvc-config.xml中添加过滤标签
<!-- 告诉前端控制器不拦截静态资源 其他文件夹类似 -->
<mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 -->
<mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 -->
<mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
或者使用默认的过滤器:
<mvc:default-servlet-handler/>
异步交互与ResponseBody响应json数据
异步交互需要使用到jQuery
前端Ajax技术格式:
$.post({
url: "${pageContext.request.contextPath}/Mtest/test6",
data: {"name": $("#txt").val()},
success: function (data, status) {
$("#sname").html(data.toString());
console.log(status);
console.log(data.toString());
}
})
json字符串和JavaBean对象互相转换的过程中,需要使用jackson的jar包:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
控制层响应设置:
// produces 防止响应json中有中文乱码问题
@RequestMapping(path = "/test7",produces = "text/html;charset=UTF-8")
//ResponseBody 注解 把user属性转换成json格式,返回给前端页面。
public @ResponseBody User test7(@RequestBody User user) {
System.out.println("test7......");
System.out.println(user);
user.setName("haha");
user.setAge(10);
return user;
}
文件上传
前端测试代码:
<form action="userUp/upload" method="post" enctype="multipart/form-data">
<%-- 注意文件上传必须有那么熟悉,否则后端得不到数据 --%>
<%-- 注释:只有设置了 name 属性的表单元素才能在提交表单时传递它们的值 --%>
文件上传:<input type="file" name="pic">
<input type="submit" value="上传">
</form>
传统方式文件上传:
文件上传依赖包:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
@RequestMapping(path = "/upload")
public String upload(HttpServletRequest request) {
System.out.println("upload.....");
String uploadPath = request.getSession().getServletContext().getRealPath("/upload/");
File path = new File(uploadPath);
if (!path.exists()) {
path.mkdirs();
}
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload fileUpload = new ServletFileUpload(factory);
try {
List<FileItem> items = fileUpload.parseRequest(request);
for (FileItem item : items) {
//检测是否是普通表单,true略过
if (item.isFormField()) {
continue;
}
String name = item.getName();
String uuid = UUID.randomUUID().toString().replace("-", "");
//写入本地磁盘
item.write(new File(path, uuid + name));
//清空缓存
item.delete();
}
} catch (Exception e) {
throw new RuntimeException("上传文件解析异常");
}
return "success";
}
SpringMVC传统方式文件上传
文件上传流程图:
<!-- 文件解析器对象,要求id名称必须是multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 配置最大上传文件大小 单位是字节 B-->
<property name="maxUploadSize" value="104857600"/>
<property name="defaultEncoding" value="utf-8"/>
</bean>
SpringMVC框架提供了MultipartFile对象,该对象表示上传的文件,要求变量名称必须和表单file标签的name属性名称相同。
@RequestMapping(path = "/upload1")
public String upload1(HttpServletRequest request, MultipartFile upload) throws IOException {
System.out.println("upload1.....");
String uploadPath = request.getSession().getServletContext().getRealPath("/upload/");
File path = new File(uploadPath);
if (!path.exists()) {
path.mkdirs();
}
//获取源文件名称
String filename = upload.getOriginalFilename();
//保存到磁盘
upload.transferTo(new File(path,filename));
return "success";
}
SpringMVC跨服务器方式文件上传
业务服务器配置:
在业务服务器端导入依赖的:
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.18.1</version>
</dependency>
参考博客:https://www.cnblogs.com/libo0125ok/p/7773898.html
跨服务器文件上传,此方法配置在业务服务器,非文件服务器
@RequestMapping(path = "/upload2")
public String upload2(MultipartFile upload) throws IOException {
System.out.println("跨服务文件测试开始");
if (upload == null) {
System.out.println("文件上传为空");
return "success";
}
//存储文件服务器地址 此文件位置一另一个tomcat 的webapp/ROOT/uploads下
String url = "http://localhost:15500/uploads/";
//获取源文件名称
String filename = upload.getOriginalFilename();
Client client = Client.create();
// 注意斜杠/问题
//和文件服务器进行连接
WebResource webResource = client.resource(url + filename);
//进行上传到文件服务器 注意转换成字节码传输
webResource.put(upload.getBytes());
System.out.println("文件上传成功");
return "success";
}
文件服务器配置:
在文件服务器tomcat中conf/web.xml配置如下
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
····
<!--关闭只读选项-->
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
····
</servlet>
异常处理(配置错误页面)
SpringMVC异常处理流程图:
- 模拟发生异常:
@RequestMapping("/exp")
//声明抛出异常
public String test2() throws Exception {
System.out.println("异常方法");
try {
//模拟服务层发生异常
int i = 1 / 0;
} catch (Exception e) {
throw new MyException("用户查询异常");
}
return "success";
}
- 自定义异常类:
public class MyException extends Exception {
private String message;
public MyException(String messeg) {
this.message = messeg;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
- 配置异常处理器:
//实现异常处理接口
@Component("myExceptionResolver")
public class MyExceptionResolver implements HandlerExceptionResolver {
//这里只是用了一个参数ex
@Override
public ModelAndView resolveException(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, Object handler, Exception ex) {
MyException me = null;
if (ex instanceof MyException) {
me = (MyException) ex;
}else {
me = new MyException("系统正在维护");
}
ModelAndView modelAndView = new ModelAndView();
//这里选择携带异常信息给前端
modelAndView.addObject("errorpage", me.getMessage());
//跳转到错误页面
modelAndView.setViewName("error");
return modelAndView;
}
}
拦截器
- 步骤:
- 编写自定义拦截器必须实现HandlerIntercept接口
- 在spring中配置拦截器
注意拦截器的执行流程:(注意点)
自定义拦截类:
public class MyIntercept implements HandlerInterceptor {
/**
* 预处理,controller方法执行前执行
*
* @return true表示放行,
* 如果有下一个拦截器执行下一个拦截器没有就,执行controller里面的方法
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("预处理拦截器执行了");
return true;
}
/**
* 后处理方法,controller方法执行后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("后处理拦截器执行了");
}
/**
* 最后执行方法,jsp等前端页面渲染完成了后执行,
* 一般用于释放资源等操作。
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("最终处理拦截器执行了");
}
}
在spring xml文件中配置拦截器:
<!-- 在spring xml文件中配置拦截器-->
<mvc:interceptors>
<!-- interceptors标签下可以配置多个自定义拦截器 -->
<mvc:interceptor>
<!-- 配置要拦截的方法 -->
<mvc:mapping path="/user/*"/>
<!-- 配置不要拦截的方法 -->
<mvc:exclude-mapping path="/"/>
<!-- 把自定义拦截器加入到spring中来 -->
<bean class="top.tdte.intercept.MyIntercept"/>
</mvc:interceptor>
<!--注意顺序问题:如配置多个拦截器,则会顺序执行-->
<mvc:interceptor>
···
</mvc:interceptor>
</mvc:interceptors>
其他问题
路径符号问题:
访问资源的时候,前面加上"/",表示绝对路径,从根路径开始去寻找资源。
访问资源的时候,前面不加"/",表示相对路径,从上一级上下文路径中去寻找资源。
SpringMVC的拦截器中:
“/” 拦截所有请求,包括静态资源的请求,但不会拦截.jsp。
“/*”会匹配一级路径,如/a,/b。
"/**"会匹配所有路径,如/a/b,/a/b/c等。
在controller类方法里获得Servlet原生对象:
只需要在控制器的方法参数定义HttpServletRequest和HttpServletResponse对象
springmvc 发送ajax中文乱码的几种解决办法:
使用spingmvc,在JS里面通过ajax发送请求,并返回json格式的数据,从数据库拿出来是正确的中文格式,展示在页面上就是错误的,因此有如下几种解决办法。
方法一: 在@RequestMapping里面加入produces = “text/html;charset=UTF-8”
@RequestMapping(value = "/configrole", method = RequestMethod.GET, produces = "text/html;charset=UTF-8")
public @ResponseBody String configrole() {
......
}
方法二:
因为在StringHttpMessageConverter里面默认设置了字符集是ISO-8859-1
所以拿到源代码,修改成UTF-8并打包到spring-web-3.2.2.jar
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String>
{
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
..........
}
方法三:
修改org.springframework.http.MediaType它的构造方法的参数,并在applicationContext-mvc.xml 加入配置
public MediaType(String type, String subtype, Charset charset) {
super(type, subtype, charset);
}
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<bean class="org.springframework.http.MediaType">
<constructor-arg value="text" />
<constructor-arg value="plain" />
<constructor-arg value="UTF-8" />
</bean>
</list>
</property>
</bean>
方法四:
直接将org.springframework.http.converter.StringHttpMessageConverter 里面的属性defaultCharset设置成utf-8
<bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8"/>
</bean>
idea中web工程不能自动打jar包问题
- 如果选择的是普通的maven工程,后添加的web支持框架,就需要手动建立lib目录,并添加依赖包。
如果选择的是maven继承的web模板,则idea会自动把依赖jar包发布到服务器。