SpringMVC
导航
一、初识SpringMVC
SpringMVC是Spring内置的一个MVC框架。比其他MVC框架更具扩展性和灵活性。
MVC是一种规范,一种思想,一种写代码的模式。
M:即model模型是指模型表示业务规则。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
V:即View视图是指用户看到并与之交互的界面。比如由html元素组成的网页界面,或者软件的客户端界面。MVC的好处之一在于它能为应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操作的方式。
C:即controller控制器是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
简单的说,三层各司其职,写出来的代码更清晰,提高了可维护性、可移植性、可扩展性等等,降低了代码的耦合性,使团队开发更简单。
底层依然是servlet,SpringMVC封装了servlet。先来看看原始的javaWeb,用sevlet写一个webapp。
导包:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
-
创建maven项目不必多说,首先我们有一个页面form.jsp:
<%@ page contentType = "text/html;charset=UTF-8" language = "java" %> <html> <head> <title>form</title> </head> <body> <form action="/hello" method="post"> <input type="text" name="method"> <input type="submit"> </form> </body> </html>
-
表单提交到了/hello,所以我们创建一个servlet,注册为/hello:
public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getParameter("method"); if (method.equals("add")) { req.getSession().setAttribute("message", "执行了add方法"); } if (method.equals("delete")) { req.getSession().setAttribute("message", "执行了delete方法"); } // 为了回显处理结果,我们将处理结果展示到这个页面上 req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
-
处理结果:
<%@ page contentType = "text/html;charset=UTF-8" language = "java" %> <html> <head> <title>test</title> </head> <body> ${message} </body> </html>
-
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"> <servlet> <servlet-name>hello</servlet-name> <servlet-class>vip.yangsf.servlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <!-- 设置默认页面 --> <welcome-file-list> <welcome-file>form.jsp</welcome-file> </welcome-file-list> </web-app>
这个web应用的功能是,当我们输入add,则显示执行add方法,输入delete则显示执行delete方法。
二、Hello SpringMVC
先搞个webapp的maven项目,还记得目录结构吗。
当然也可以不用这种结构,但你后续在输出目录里面添加依赖的样子真的很狼狈。
配置tomcat(如果不会的话,看这个https://blog.csdn.net/yangsf_/article/details/123980675?spm=1001.2014.3001.5501)。
先导包:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
然后直接开始做,之后会解释每一步:
- 编写在WEB-INF/jsp下编写jsp
<%@ page contentType = "text/html;charset=UTF-8" language = "java" %>
<html>
<head>
<title>hello-mvc</title>
</head>
<body>
${message}
</body>
</html>
-
配置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" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/> <bean 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"> <servlet> <servlet-name>hello-spingMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springMVC-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hello-spingMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
编写controller,并在spring中注册
public class HelloMVC implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("message", "hello springMVC"); modelAndView.setViewName("hello"); return modelAndView; } }
<bean class="vip.yangsf.controller.HelloMVC" id="/hello"/>
-
执行tomcat,访问/hello
第一个SpringMVC程序就完成了。
三、SpringMVC执行流程
第一步不必说,就是从session中取出key为message的value。
说后面之前,我们先来看两张图:
SpringMVC执行流程:
继承链:
- DispatcherServlet,官方把它称为前端控制器,有了它之后,我们的请求都应该经过它去调度,因为它的父类的父类的……实现了servlet接口,所以实际上他也是一个servlet,所以要在web.xml中注册。
- web.xml中DispatcherServlet的参数意思是,用配置文件配置DispatcherServlet,并且让所有请求都经过它。
- “/”和“/*”的区别,“/”表示请求匹配所有请求但不包括.jsp,”/*“包括.jsp。
- 为什么在Spring配置文件中的BeanNameUrlHandlerMapping、SimpleControllerHandlerAdapter、InternalResourceViewResolver?看执行流程就明白了。
虽然已经比原生servlet简单,但是还是很复杂,很繁琐,所以我们以后不这么玩,注解才是SpringMVC的精髓。
四、 真正的Hello SpringMVC(注解版)
接下来使用注解实现Hello SpringMVC。
-
新建webapp项目,导包,配置tomcat、编写jsp,一样的
<%@ page contentType = "text/html;charset=UTF-8" language = "java" %> <html> <head> <title>hello-mvc</title> </head> <body> ${message} </body> </html>
-
在web.xml中注册DispatcherServlet,也是一样的
<?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"> <servlet> <servlet-name>hello-spingMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springMVC-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hello-spingMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
配置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 https://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"> <context:component-scan base-package="vip.yangsf.controller"/> <mvc:default-servlet-handler/> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
当然,视图解析器还是要,但不用再注册那几个bean了。我们直接开启包扫描,开启静态文件过滤,开启mvc注解驱动!
-
编写controller
@Controller public class HelloSpringMVC { @RequestMapping("/hello") public String hello(Model model) { model.addAttribute("message", "Hello SpringMVC"); return "hello"; } }
就是如此的清爽,第七行添加属性都看得懂,要说的是:
- return后面的字符串就是jsp的文件名
- @Controller表示在IoC容器中注册了一个bean。(类似于@Component)
- @RequestMapping(“/hello”)表示请求地址为/hello
-
效果:
还可以在Controller里面添加多个方法,指定不同的request映射,指向同一页面,可以实现页面复用。
五、RestFul风格
要知道什么是RestFul风格,首先要知道什么是Rest。
rest是一种软件架构风格,不是规范,就是一种风格,满足这种风格的就叫做RestFul。
RESTFUL特点包括:
1、每一个URI代表1种资源;
2、客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
3、通过操作资源的表现形式来操作资源;
4、资源的表现形式是XML或者HTML;
5、客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。
理论说的头晕,直接看例子:
先写一个不是restful风格的webapp:
配置就不再赘述了,直接看controller层
@Controller
public class Test01 {
@RequestMapping("/test01")
public String test01(int a, String b, Model model) {
String res = a + b;
model.addAttribute("message", "test01:" + res);
return "test";
}
}
我们的程序可以接收两个参数,一般情况下,我们是这样传递参数的:
http://localhost:8080/test01?a=1&b=2
如果使用restful风格,那么是这样的:
http://localhost:8080/test01/1/2
如何实现呢?
几个注解就完事了:
第一种方法:
@Controller
public class Test01 {
@RequestMapping(value = "/test01/{a}/{b}", method = RequestMethod.GET)
// @PathVariable表示可以将参数值用在url中
public String test01(@PathVariable int a, @PathVariable String b, Model model) {
String res = a + b;
model.addAttribute("message", "test01:" + res);
return "test";
}
}
我们可以用不同的请求方法区别不同的资源
第二种方法(推荐):
@Controller
public class Test01 {
@GetMapping("/test01/{a}/{b}")
public String test01(@PathVariable int a, @PathVariable String b, Model model) {
String res = a + b;
model.addAttribute("message", "test01:" + res);
return "test";
}
}
结果也是一样的。
为什么需要用不同的请求方法区别不同的资源?假如有这么三种情况:
- /account/1:得到id=1的account
- /account/1:删除id=1的account
- /account/1:更新id=1的account
url完全相同,就需要用请求方法区分了。
restful优点:
- 轻量,直接基于http,不在需要任何别的诸如消息协议。get/post/put/delete为CRUD操作
- 面向资源,一目了然,具有自解释性。
- 数据描述简单,一般以xml,json做数据交换。
- 安全,不用暴露参数信息。
缺点:
一个适用于简单操作的接口规范而已,无规矩不成方圆,复杂操作并不适用,还是看业务发展需求的
适合CRUD并且只适合CRUD,有的浏览器可能不支持POST、GET之外的提交方式,要特殊处理,API容易给让误解中能进行增、删、查、改等操作。
六、非正常人操作(重定向和转发)
为什么说是非正常人操作,因为接下来我们会注释掉视图解析器,来搞一些没用的(但是可以帮助我们深入理解SpringMVC)。
先注掉springmvc配置文件中的视图解析器。
学到这里,都知道Controller层里面返回值为String的方法,视图解析器都会把它们的返回值进行拼接处理,或是转发到某页面。
如果不用return的方式。。
jsp同样是获取message的值,为了测试方便,我们把这个jsp放在webapp目录下。
Controller层这样写:
@Controller
public class Test666 {
@RequestMapping("/test01")
public void test01(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().setAttribute("message", "test01");
request.getRequestDispatcher("/test.jsp").forward(request, response);
}
}
发现是可以成功访问到/test.jsp页面的,为什么呢?
-
可以接收HttpServletRequest,以及HttpServletResponse,就像在写servlet一样。
-
我们所有的请求都会经过DIspatcherServlet的doService方法
还可以这样写:
@RequestMapping("/test02")
public String test02(Model model) {
model.addAttribute("message", "test02");
return "/test.jsp";
}
道理也是一样的,自己把路径写了,不用视图解析器拼接。
6.2 来点正经的(使用SpringMVC进行转发或者重定向)
很简单,默认为转发,也可以显示的表示:
@RequestMapping("/test02")
public String test02() {
return "forward:/index.jsp";
}
重定向:
@RequestMapping("/test02")
public String test02() {
return "redirect:/index.jsp";
}
重定向很少用,基本上是转发。
如果配置了视图解析器,冒号后面直接写文件名就行了。
七、接收请求参数以及数据回显
7.1 接收参数
分几种情况
-
提交的域名和处理方法的参数名一致
@Controller public class ParamTest { @RequestMapping("/test01") public String test01(String name, Model model) { model.addAttribute("message", name); return "test"; } }
没有什么变化,和以前一样。
-
提交的域名和处理方法的参数名不一致
这个时候就需要加上一个注解@RequestParam,最佳实践是,无论参数名是否一致,只要是前端需要使用的都加上这个注解。
@Controller public class ParamTest { @RequestMapping("/test01") public String test01(@RequestParam("username") String name, Model model) { model.addAttribute("message", name); return "test"; } }
将请求参数变为username,依然能正常回显:
-
处理方法参数为一个对象
有如下实体类:
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String pwd; }
controller:
@Controller public class ParamTest { @RequestMapping("/test02") private String test02(User user) { System.out.println(user);java return "test"; } }
-
只要字段名和请求参数名一致,则无须处理,如果不一致,则是null
请求
http://localhost:8080/test02?id=1&name=root&pwd=123456
控制台输出
User(id=1, name=root, pwd=123456)
7.2 返回参数
现在我们的Controller能够接收请求的参数了,如何把数据返回给前端呢?
这里说三种方式,其中有两种是之前用的
-
ModelAndView 最先开始用的,这里不再赘述。
-
Model 刚刚一直在用的,也不再赘述。
-
ModelMap,比Model功能更强大,继承自LinkedHashMap,可以接收一组数据
一般情况下就用Model就行了。
7.3 解决乱码问题
如果不设置编码,当我们要回显中文或是接收中文时,就可能会乱码,如何解决乱码?
还记得JavaWeb学的Filter过滤器吗?
创建一个过滤器类,实现Filter接口:
public class AFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf8");
servletResponse.setContentType("text/html;utf8");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
在web.xml中注册:
<filter>
<filter-name>Afilter</filter-name>
<filter-class>vip.yangsf.filter.AFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Afilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这样就可以解决乱码问题,但是麻烦,还要自己写过滤器。
SpringMVC早帮我们写好了过滤器,我们只需要注册就可以:
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意:请注意过滤路径是”/*“而不是”*“
八、Json
JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
总之,我们给前端传输数据,或者前端传数据给我们,那么json是一个不错的选择,json就是一个字符串,而且规则很简单:
- 数据在 key/value 对中
- 数据由逗号分隔
- 大括号 { } 保存对象
- 中括号 [ ] 保存数组
来吧看个实例:
{
"name": "John Doe",
"age": 18,
"address":
{
"country": "china",
"zip-code": "10000"
}
}
json教程网上足够多,现在来看看我们在java中如何处理把对象转换成json或是处理json。
8.1 Jackson
Jackson是一个简单基于Java应用库,Jackson可以轻松的将Java对象转换成json对象和xml文档,同样也可以将json、xml转换成Java对象。Jackson所依赖的jar包较少,简单易用并且性能也要相对高些,并且Jackson社区相对比较活跃,更新速度也比较快。
---- 易百教程
使用Jackson的步骤:
-
第一步永远是导包:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency>
-
web.xml配置DispatcherServlet以及配置SpringMVC这里就不再说了。
-
User类:
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String pwd; }
-
编写controller层
之前我们写controller,会用上@Controller 以及 @RequestMapping这些注解,当我们请求@RequestMapping的value时,就会经过视图解析器,渲染到 return 指定的页面上。
现在我们多加一个@ResponseBody,表示不经过视图解析器,直接将return后面的字符串写入到Response的body中,当前端访问@RequestMapping的时候,就会获得一个json对象,这个方法就是暴露给前端的接口。
如果我们不传json,是这样:
@Controller public class NoJSON { @RequestMapping("/noJSON01") @ResponseBody public String test01() { User user = new User(1, "root", "123456"); return user.toString(); } }
-
使用Jackson
-
传一个user
每个类上面都写@ResponseBody太繁琐了,因为我们以后的Controller层都是给前端写接口,不用我们去渲染页面,我们只需要将@Controller改为@RestController即可。
@RestController public class NoJSON { @RequestMapping("/noJSON01") public String test01() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); User user = new User(1, "root", "123456"); String s = objectMapper.writeValueAsString(user); return s; } }
当我请求/noJSON01就会获得一个json字符串。
-
传一组user
也是一样的
@RequestMapping("/test01") public String test01() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); List<User> users = new ArrayList<>(); User user1 = new User(1, "root", "123456"); User user2 = new User(2, "root", "123456"); User user3 = new User(3, "root", "123456"); User user4 = new User(4, "root", "123456"); User user5 = new User(5, "root", "123456"); User user6 = new User(6, "root", "123456"); users.add(user1); users.add(user2); users.add(user3); users.add(user4); users.add(user5); users.add(user6); String s = objectMapper.writeValueAsString(users); return s; }
小小的总结一下:
- 创建objectMapper实例
- 调用writeValueAsString方法
那么传时间呢?
传直接传Date过去是一个时间戳,于是我们可以用SimpleDateFormat来转换一下:
@RequestMapping("/test02") public String test02() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); Date data = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String s = objectMapper.writeValueAsString(simpleDateFormat.format(data)); return s; }
但其实Jackson里面就自带格式时间的配置:
@RequestMapping("/test03") public String test03() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); Date data = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); objectMapper.setDateFormat(simpleDateFormat); String s = objectMapper.writeValueAsString(data); return s; }
使用Jackson,步骤就那几步规定死了,于是我们写一个工具类:
public class JsonUtils { public static String getJson(Object object) { return getJson(object, "yyyy-MM-dd HH:mm:ss"); } public static String getJson(Object object, String dateFormat) { ObjectMapper objectMapper = new ObjectMapper(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); objectMapper.setDateFormat(simpleDateFormat); try { return objectMapper.writeValueAsString(object); } catch (JsonProcessingException e) { e.printStackTrace(); } return null; } }
以后无论是什么对象,只需要调用getJson就完事了:
@RequestMapping("/test04") public String test04(){ Date date = new Date(); String s = JsonUtils.getJson(date); return s; }
@RequestMapping("/test01") public String test01() throws JsonProcessingException { List<User> users = new ArrayList<>(); User user1 = new User(1, "root", "123456"); User user2 = new User(2, "root", "123456"); User user3 = new User(3, "root", "123456"); users.add(user1); users.add(user2); users.add(user3); String s = JsonUtils.getJson(users); return s; }
-
解决乱码问题
如果乱码,原始的方法是在@RequestMapping后面添加参数,如
@RequestMapping(value = "/test01", produces = "application/json;charset=UTF-8")
但SpringMVC已经帮我们做好了一切,我们只需要添加配置:
<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>
Jackson总结:
编写工具类,然后调用方法就完事。
8.2 FastJson
FastJson也很简单,是阿里巴巴开发的。
Jackson学习中我们已经了解了很多关于Json的东西了。所以这里不再解释,直接用:
传对象:
@RequestMapping("/fastjson")
public String test02() {
User user = new User(1, "root", "123456");
String s = JSON.toJSONString(user);
return s;
}
传时间:
@RequestMapping("/fastjson")
public String test02() {
String s = JSON.toJSONStringWithDateFormat(new Date(), JSON.DEFFAULT_DATE_FORMAT);
return s;
}
json字符串转java对象:
@RequestMapping("/fastjson")
public String test02() {
String Juser = "{\"id\":1,\"name\":\"root\",\"pwd\":\"123456\"}";
User user = JSON.parseObject(Juser, User.class);
System.out.println(user);
return user.toString();
}
Java对象转Json对象:
@RequestMapping("/fastjson")
public String test02() {
User user = new User(1, "root", "123456");
JSONObject jsonObject = (JSONObject) JSON.toJSON(user);
System.out.println(jsonObject);
return jsonObject.toJSONString();
}
Json对象转Java对象:
@RequestMapping("/fastjson")
public String test02() {
User user = new User(1, "root", "123456");
JSONObject jsonObject = (JSONObject) JSON.toJSON(user);
System.out.println(jsonObject);
User Juser = JSON.toJavaObject(jsonObject, User.class);
System.out.println(Juser);
return jsonObject.toJSONString();
}
解决乱码SpringMVC配置:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
不知道这个解决乱码靠不靠谱,我用着还行。
九、AJAX
Ajax即Asynchronous Javascript And XML(异步JavaScript和XML)在 2005年被Jesse James Garrett提出的新术语,用来描述一种使用现有技术集合的‘新’方法,包括: HTML 或 XHTML, CSS, JavaScript, DOM, XML, XSLT, 以及最重要的XMLHttpRequest。使用Ajax技术网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面,这使得程序能够更快地回应用户的操作。
关键:不需要重载(刷新)整个页面(局部刷新), 异步。
什么是异步?
学多线程的时候我们了解了同步,当时的案例是银行取钱,当多个线程要操作同一个资源时,我们要为其加上锁,拿到锁的人才能够操作。
也就是得一个一个来,这个执行完了才能执行下一个。
异步正好相反,大家可以同时操作,不用排队。接收一个任务,就丢给后台,再接取下一个任务,谁执行完了就直接返回,没有顺序。
知道了什么是异步,来体验一下无刷新就可以更新页面的感觉(不是真正的ajax):
<iframe>标签
<div style="text-align: center;">
<input type="text" id="url">
<input type="button" onclick="go()" value="测试">
</div>
<div style="text-align: center;">
<iframe src="" style="width: 80%; height: 800px;"></iframe>
</div>
<script>
var _url = document.getElementById("url");
var _iframe = document.getElementsByTagName("iframe")[0];
function go() {
_iframe.src = _url.value;
}
</script>
在文本框里输入url,就可以在下面的框框内显示,而不是刷新整个页面。
现在知道了什么是无刷新和异步,就来看看ajax怎么用吧。
ajax提升注册(登录)体验
我们需要实时的告诉用户输入的用户名,或密码符不符合要求。
思路:获取用户输入,传到后台验证,后台返回验证结果,前端给用户提示。
说干就干
页面(jsp):
<head>
<script src="${pageContext.request.contextPath}/static/http_cdn.staticfile.org_jquery_3.6.0_jquery.js"></script>
</head>
<body>
<p>
用户名:<input type="text" name="name" id="name" οnblur="a()"> <span id="nameInfo"></span>
</p>
<p>
密码:<input type="text" name="pwd" id="pwd" οnblur="b()"> <span id="pwdInfo"></span>
</p>
<script>
let _name = $('#name');
let _pwd = $('#pwd');
let _nameInfo =$('#nameInfo');
let _pwdInfo =$('#pwdInfo');
function a() {
$.ajax({
url:"${pageContext.request.contextPath}/ajax03",
data: {"name":_name.val()},
success:function (data) {
if (data == 'ok') {
_nameInfo.css("color","green");
} else {
_nameInfo.css("color","red");
}
_nameInfo.html(data);
}
})
}
function b() {
$.ajax({
url:"${pageContext.request.contextPath}/ajax03",
data:{"pwd":_pwd.val()},
success:function (data) {
if (data == 'ok') {
_pwdInfo.css("color","green");
} else {
_pwdInfo.css("color","red");
}
_pwdInfo.html(data);
}
})
}
</script>
</body>
两个文本框,在没有获取焦点的情况下执行某个函数。
函数里面执行ajax方法,url表示向哪里发出请求(后台发),data表示传过去的数据,success表示成功后执行的函数,这个函数可以接收后端返回的数据,还可以接收状态等等。
后端(模拟一下,真实情况会到数据库中查找):
@RequestMapping("/ajax03")
@ResponseBody
public String ajax03(String name, String pwd) {
StringBuilder stringBuffer = new StringBuilder();
if (name != null) {
if ("root".equals(name)) {
stringBuffer.append("ok");
} else {
stringBuffer.append("用户名错误");
}
}
if (pwd != null) {
if ("123456".equals(pwd)) {
stringBuffer.append("ok");
} else {
stringBuffer.append("密码错误");
}
}
return stringBuffer.toString();
}
接收前端传进来的data并给前端返回data。
最后就可以把这个数据填充到文本框后面的span标签里面。
十、拦截器
10.1 拦截器和过滤器的区别
和过滤器类似,但和过滤器有区别:
-
拦截器基于java的反射机制,而过滤器是基于函数回调
-
拦截器不依赖于servlet容器,过滤器依赖servlet容器
-
拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用
-
在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
5. 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器注入一个service,可以调用业务逻辑
10.2 实现拦截器
创建一个maven然后把spring和springmvc的基本配置配好。
实现HandlerInterceptor接口就是拦截器:
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("======= 预处理 ========");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("========处理后========");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("========清理========");
}
}
-
注册到spring中
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="vip.yangsf.config.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors>
意为所有的请求都会走拦截器,如果preHandle方法返回true则放行,如果为false则拦截。
实践一下。
10.3 登录验证实现
用户访问/user/**时,会经过拦截器。
前端页面:
index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<h1>
<a href = "${pageContext.request.contextPath}/user/goLogin">登录</a>
</h1>
<h1>
<a href = "${pageContext.request.contextPath}/user/goMain">首页</a>
</h1>
</body>
</html>
/WEB-INF/jsp下的页面:
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">
用户名:<input type = "text" name="username">
密码:<input type = "text" name="password">
<input type = "submit" value="登录">
<a href="${pageContext.request.contextPath}/">返回</a>
</form>
</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}/">返回</a>
<a href="${pageContext.request.contextPath}/user/goOut">注销</a>
</body>
</html>
Controller:
@Controller
@RequestMapping("/user")
public class MyController {
@RequestMapping("/test01")
@ResponseBody
public String test01() {
return "hello";
}
@RequestMapping("/login")
public void login(@RequestParam("username") String name, String password, HttpSession session, HttpServletResponse response) throws IOException {
session.setAttribute("userInfo", name);
response.sendRedirect("/user/goMain");
}
@RequestMapping("/goMain")
public String main() {
return "main";
}
@RequestMapping("/goLogin")
public String goLogin() {
return "login";
}
@RequestMapping("/goOut")
public String goOut(HttpSession session) {
session.removeAttribute("userInfo");
return "login";
}
}
拦截器:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(666);
HttpSession session = request.getSession();
boolean flag = "root".equals(session.getAttribute("userInfo")) || request.getRequestURI().contains("login");
if (!flag) {
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
}
return flag;
}
}
Spring配置:
<?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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="vip.yangsf.controller"/>
<mvc:default-servlet-handler/>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="utf8"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/**"/>
<bean class="vip.yangsf.config.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
这样就完成了一个登录验证的功能。
十一、文件上传与下载
无须记住,需要的时候copy就行,基本上是死代码。
导包:
<dependencies>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
11.1 文件上传
Spring为我们提供了文件上传文件的功能,不过需要手动配置:
<?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
https://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">
<context:component-scan base-package="vip.yangsf.controller"/>
<mvc:default-servlet-handler/>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="utf8"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
<property name="defaultEncoding" value="utf-8"/>
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
</beans>
CommonsMultipartResolver的id必须是multipartResolver。
前端页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<form action = "${pageContext.request.contextPath}/upload01" enctype="multipart/form-data" method="post">
<input type="file" name="file">
<input type="submit" value="upload">
</form>
</body>
</html>
注意:
enctype必须为"multipart/form-data"
设置为multipart/form-data后,会以二进制的形式传递数据。
Controller:
两种方式实现文件上传:
-
流处理:
@Controller public class MyController { @RequestMapping("upload01") public String fileUpload(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException { // 获取文件名 String uploadFileName = file.getOriginalFilename(); // 判断文件名是否为空 if ("".equals(uploadFileName)) { return "redirect:index.jsp"; } 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 "redirect:index.jsp"; } }
-
用Spring封装好了的方法:
@RequestMapping("upload02") public String fileUpload02(@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 + file.getOriginalFilename()); file.transferTo(new File(realPath + "/" + file.getOriginalFilename())); return "redirect:index.jsp"; }
简便多了。
11.2 文件下载
无须记住,能看懂就行了,下载也就是通过设置response来下载。
用a标签就可以直接下载文件(除了图片)。
代码为下载根路径下的upload文件夹中的 大一名单.xlsx 文件
@RequestMapping("download")
public String fileDownload(HttpServletResponse response, HttpServletRequest request) throws IOException {
String path = request.getServletContext().getRealPath("/upload");
String fileName = "大一名单.xlsx";
//设置响应头
response.reset();
response.setCharacterEncoding("utf8");
response.setContentType("multipart/form-data");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf8"));
File file = new File(path, fileName);
InputStream inputStream = new FileInputStream(file);
OutputStream outputStream = response.getOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while((len = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, len);
outputStream.flush();
}
outputStream.close();
inputStream.close();
return null;
}