SpringMVC
@CrossOrigin:解决浏览器跨域问题
1、Spring MVC底层执行的流程
一次请求的流程: 客户端发送请求 ==> 请求分发器DispatcherServlet ==> 调用处理器映射HandlerMapping,找到对应的@RequestMapping ==> 调用处理器适配器HandlerAdapter找到对应的方法(在执行请求处理器之前,HandlerAdapter会将请求参数进行适配和封装,使其符合处理器方法的参数类型和顺序。执行完请求处理器后,HandlerAdapter会将返回值进行适配和封装,使其符合Servlet API规范中的要求) ==> 调用视图解析器ViewResolver,也就是我们配置的InternalResourceViewResolver,会根据DispatcherServlet中的ModelAndView中的view来返回具体的视图对象,视图对象渲染模型数据,生成对应的页面(如:/WEb-INF/views/index.jsp) ==> DispatcherServlet将页面响应回去给客户端
2、DispatcherServlet请求分发器、HandlerMapping处理器映射、HandlerAdapter处理器适配器、ViewResolver视图解析器分别的作用
DispatcherServlet请求分发器 : 拦截请求,根据请求找到对应的controller,再讲请求分发给对应的controller
**HandlerMapping处理器映射:**找到对应的@RequestMapping()
**HandlerAdapter处理器适配器:**在请求前把前端提交过来的数据,根据方法中的参数类型进行数据封装和转换;在请求后HandlerAdapter会将返回值进行适配和封装
**ViewResolver视图解析器:**将ModelAndView中的view进行匹配完整的视图,并将model中的数据进行渲染上去,返回给DispatcherServlet,由DispatcherServlet将页面显示给客户端
1、客户端发送 HTTP 请求到服务器端,由 DispatcherServlet 进行拦截。
2、DispatcherServlet 根据请求的 URL 调用处理器映射器(HandlerMapping),将请求映射到对应的控制器@RequerstMapping()
3、发现对应的控制器后,DispatcherServlet 根据处理器适配器(HandlerAdapter)调用该控制器的方法,负责请求参数的适配和封装(自动转成对应类型,或封装成对应的对象),以及返回值的适配和封装。
4、控制器处理完请求后,将处理结果封装在 ModelAndView 对象中返回给 DispatcherServlet,该对象包含了视图名称和模型数据。
5、DispatcherServlet 根据视图名称调用视图解析器(ViewResolver)进行解析,返回具体的视图对象(如 JSP)。
6、视图对象渲染模型数据,生成对应的 HTML 页面。
7、DispatcherServlet 将生成的 HTML 页面作为响应发送给客户端。
在整个流程中,处理器映射器(HandlerMapping)、处理器适配器(HandlerAdapter)和视图解析器(ViewResolver)起到关键作用。处理器映射器负责根据请求的 URL 将请求映射到对应的控制器,处理器适配器负责处理控制器方法的调用,而视图解析器负责根据视图名称解析对应的视图对象。
总的来说,Spring MVC 框架经过了前端控制器、处理器映射器、处理器适配器、视图解析器等组件的协同工作,实现了请求的接收与响应的处理,基于组件化、模块化的思想逐步剥离出代码中的耦合,以便于更加灵活和可维护的开发。
3、springmvc中的vo对象的设计
比如:
实体类User:用户名、密码、生日、年龄、爱好…20个(列太多,前端提交用不到)
前端 UserVo:用户名、密码(登录只需要提交两个数据)
这时就可以细分出来一个UserVo对象,UserVo对象是拆分了User中一些前端不需要的数据而细分出来的,有的时候可能其中还包含权限字段,但是这个字段不需要前端提交过来,需要自己在业务层手动添加进去,这时就可以设计出来一个UserVo对象用来封装前端提交的数据,其他的就不用自动封装,在service层手动封装
可以使用BeanUtils.copyProperties(obj1,obj2),把obj1对象中字段的值复制到obj2对象中对应的字段上;例如BeanUtils.copyProperties(UserVo,User),需要两个对象中字段名要一致
一·、使用步骤
1、配置xml
<context:component-scan base-package="cn.wolfcode1"/>
<mvc:annotation-driven/> //springMVC扫描器
2、配置前端控制器web.xml
为什么要加标 签,因为DispatcherServlet默认是从WEB-INF目录下去找默认的servletName-servlet.xml中的路径去加载SpringMVC配置文件。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置在启动服务器时就加载mvc.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc2.xml</param-value>
</init-param>
<!-- Tomcat 启动初始化 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern> //不能配置成/*,因为/*表示匹配所有的资源,包含.jsp,如果配置了/*在跳转到jsp的时候会自动匹配.jsp,再有视图解析器拼接上就变成xxx.jsp.jsp了
</servlet-mapping>
</web-app>
3、创建一个controller类
@Controller
public class TestController {
@RequestMapping("/request1")
public ModelAndView modelAndView(){
ModelAndView mv = new ModelAndView();
mv.addObject("msg","ABC");
mv.setViewName("/WEB-INF/views/hello.jsp");
return mv;
}
}
二、配置编码过滤器
<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>
三、配置了资源路径的前缀和后缀
-- 配置了前端控制器,会默认覆盖掉servlet中的静态资源路径
<mvc:default-servlet-handler/>
//不覆盖servlet默认的资源路径,在访问静态资源如:图片,css、js等就会自动让默认的servlet来处理,而不会经过dispatcherServlet,设置这个就不会覆盖servlet默认的静态资源管理
<mvc:annotation-driven />
<!--它会注册Spring MVC框架的注解驱动处理器,包括注解映射处理器、数据绑定处理器和参数解析器。-->
//配置资源路径的前后缀
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
如果配置了资源路径的前后缀之后就不能直接跳转到指定路径的资源中了,只能使用转发或者重定向来实现
@RequestMapping("/test")
public String test1(){
return "forward:/WEB-INF/views/hello.jsp";
//return "redirect:/index.html";
}
四、请求的参数并封装
1、一个参数时
-- 浏览器请求的参数名字必须要和方法的形参名一致才能赋值
@RequestMapping("/request2")
public String request2(String name){
System.out.println(name);
return "hello";
}
2、一个类型的参数,多个值时(数组)
-- 浏览器发送的请求 如下
http://localhost/request3?ids=3&ids=8
@RequestMapping("/request3")
public String request3(Long[] ids){
System.out.println(Arrays.toString(ids));
return "hello";
}
3、多个参数不同类型时
--- 当参数名和浏览器传递的变量名不一致,可以使用@RequestParam("username")来设置名字来接收浏览器的数据用来封装到对应的属性上
@RequestMapping("/request4")
public String request4(@RequestParam("username") String name, Integer age){
System.out.println(name);
System.out.println(age);
return "hello";
}
4、日期格式解析
@RequestMapping("/dateFormat")
public String dateFormat(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date){
System.out.println(date);
return "hello";
}
public class User { //可以在对象中对应的字段加上解析
private Long id;
private String username;
private String password;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date date;
}
5、对象类型数据封装
@RequestMapping("/user")
public String userTest(User user){
System.out.println(user);
return "hello";
}
6、文件上传,需要在web.xml中加上
--- 如果没有配置以下代码,那就不支持文件上传
<multipart-config>
<max-file-size>52428800</max-file-size>
<max-request-size>52428800</max-request-size>
</multipart-config>
@RequestMapping("/fileUpload")
public String upload(Part pic) throws IOException {
System.out.println(pic.getName());
System.out.println(pic.getContentType());
System.out.println(pic.getHeaderNames());
//FileCopyUtils.copy(part.getInputStream(),new FileOutputStream(new File(""))); //文件保存
return "hello";
}
五、拦截器
作用 : 可以作为日志记录,登录验证等
HandlerInterceptor是Spring MVC中的拦截器接口,它有三个方法:
preHandle:在请求处理之前进行调用,返回值决定是否中断请求处理链,如果返回false,则中断请求处理,不会进入Controller中的方法。
postHandle:在请求处理之后进行调用,但是在视图渲染之前,可以对ModelAndView进行操作。
afterCompletion:在整个请求处理完毕后进行调用,可以在该方法中进行一些资源清理的操作。
这三个方法的具体用法如下:
preHandle:在请求处理之前进行调用,可以实现对请求的拦截和处理,常用于权限控制、日志记录等。如果返回true,则请求处理将会继续进行;如果返回false,则请求处理将会中断。
postHandle:在请求处理之后进行调用,在视图渲染之前,可以对ModelAndView进行操作,常用于添加公共的视图变量等操作。
afterCompletion:在整个请求处理完毕后进行调用,可以进行一些资源清理的操作,常用于日志记录、异常处理等操作。
1、创建一个类实现HandlerInterceptor接口(需要点进去复制并覆盖其方法,因为它内部使用是java8新特性,接口的default默认方法)
public class MyInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("进入控制器前");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
}
实例:登录拦截
1、创建一个拦截器实现类或者继承类
1.1、实现方式(建议): HandlerInterceptor 接口,并重写其三个方法 preHandle、postHandle、afterCompletion。
1.2、继承方式: HandlerInterceptorAdapter 类,并重写其三个方法 preHandle、postHandle、afterCompletion。此方式实现拦截器相对于第一种方式更加简洁。
==========================代码如下==============================
public class LoginInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入控制器前");
request.setCharacterEncoding("UTF-8");
User user = (User) request.getSession().getAttribute("user");
if (user == null) {
request.setAttribute("msg","请输入账号和密码");
request.getRequestDispatcher("/login.jsp").forward(request, response);
return false;
}
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
System.out.println("postHandle()...");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
System.out.println("after()....");
}
}
========================================================
2、配置mvc.xml配置拦截器
<!--配置拦截器,可以配置多个拦截器链,和 filter差不多-->
<mvc:interceptors>
<!--注册拦截器,登录验证-->
<mvc:interceptor>
<!--拦截的请求-->
<mvc:mapping path="/student/*"/>
<!--放行的请求-->
<!--<mvc:exclude-mapping path=""/>-->
<bean id="loginInterceptor" class="cn.wolfcode.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
2、在mvc.xml中配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<mvc:exclude-mapping path="/index.html"/> <!-- 放行 -->
<bean id="interceptor" class="cn.wolfcode1.web.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
3、测试
@RequestMapping("/encoding")
public String encoding(User user){
System.out.println(user);
return "hello";
}
六、日期参数类型的接收和处理
--- 单独使用Date参数接收
@RequestMapping("/date")
public String date(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date){
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");//解析日期类型的参数
System.out.println(date);
System.out.println(sf.format(date));
return "";
}
--- //全局日期处理,注册一个注解,用来解析本类中所有的日期类型,自动转换
@InitBinder
public void initBinder(WebDataBinder binder){
binder.registerCustomEditor(Date.class,new CustomDateEditor(sf,true));
}
@RequestMapping("/date")
public String date(Date date){ //就可以不用使用@DateTimeFormat注解了
System.out.println(date);
System.out.println(sf.format(date));
return "";
}
--- 在javaBean中的某个字段是Date类型时
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date date;
--- 把json格式的日期转成解析到对应的字段上
@JsonFormat(pattern = "yyyy‐MM‐dd HH:mm", timezone = "GMT+8")
private Date appointmentTime;
七、RestFul风格
原来的请求:http://localhost/test1?a=4&b=6
@RequestMapping("/test1")
public String test1(int a, int b, Model model){
int result = a + b;
model.addAttribute("msg","结果为"+result);
return "test";
}
使用RestFul的请求:http://localhost/test1/10/6
@RequestMapping("/test1/{a}/{b}")
public String test1(@PathVariable int a,@PathVariable int b, Model model){
int result = a + b;
model.addAttribute("msg","结果为"+result);
return "test";
}
使用RestFul指定mapping类型,指定了请求的类型,如果不适用该类型来发送请求会报405错误,其他类型的请求可以使用ajax发送请求
@GetMapping、@PostMapping、@DeleteMapping、@PutMapping…
八、文件上传
1、先导入依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
2、在mvc.xml中加入文件上传的bean
<!--文件上传配置-->
<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>
3、jsp页面
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file"/><br>
<input type="text" name="username"><br>
<input type="text" name="password"><br>
<input type="submit" value="upload">
</form>
4、控制层controller
@RestController
public class FileController {
@RequestMapping("/upload")
public String upload(@RequestParam("file") CommonsMultipartFile file, User user,HttpServletRequest req) throws IOException, IOException {
System.out.println(user);
//上传路径保存位置
String path = req.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()) {
realPath.mkdir();
}
//上传文件地址
System.out.println("上传文件保存地址:"+realPath);
//通过CommonsMultipartFile的方法直接写文件
file.transferTo(new File(realPath + "/" + file.getOriginalFilename()));
return "redirect:/index.jsp";
}
}
注:@RestController相当于@Controller和@ResponseBody,将结果转成json格式,和支持restFul风格,支持@GetMapping、@PostMapping、@DeleteMapping、@PutMapping…
九、前后端实现json传送数据
1、后端代码
**问题:**后端返回的数据带中文时,在前端获取数据的时候会出现乱码?
**解决方法:**原因是spring源码中@ResponseBody 的实现类发现其默认的编码是 iso-8859-1,而项目用的编码为utf-8,所以传中文会出现乱码
解决办法 在requetMapping 如下加上这段代码
produces = “text/html; charset=UTF-8”
@Controller
public class VueController {
@RequestMapping(value = "/vue",method = RequestMethod.POST,produces = "text/html; charset=UTF-8")
//
@ResponseBody
public String getVue(Account ac,Model model){
System.out.println(ac);
Account account = new Account(1L, "张三", "123");
/* Map<String, Object> map = new HashMap<>();
map.put("name","张三");*/
model.addAttribute("account",account);
return JSON.toJSONString(account);
}
}
2、前端代码
<script>
$("#btn").click(function () {
$.ajax({
url:"/vue",
type:"post",
data:{
id:1,
username:"zs",
password:"1234"
},
success:function (data) {
console.log(data)
}
})
})