文章目录
一、MVC架构
1、概念
名称 | 职责 |
---|---|
Model | 模型:即业务模型,负责完成业务中的数据通信处理,对应项目中的 service和dao |
View | 视图:渲染数据,生成页面。对应项目中的Jsp |
Controller | 控制器:直接对接请求,控制MVC流程,调度模型,选择视图。对应项目中的Servlet |
2、好处
- MVC是现下软件开发中的最流行的代码结构形态;
- 人们根据负责的不同逻辑,将项目中的代码分成 M V C 3个层次;
- 层次内部职责单一,层次之间耦合度低;
- 符合低耦合 高内聚的设计理念。也实际有利于项目的长期维护。
二、SpringMVC的具体实现步骤
1、xml配置版
1.1 在pom.xml文件中添加依赖
<!--SpingMVC的依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--servlet的依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency>
1.2 配置web.xml
<!--配置DispatcherServlet:SpringMVC的核心,请求分发器,前端控制器--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--声明spring配置文件的位置,这个如果不写,默认是在/WEB-INF/springmvc-servlet.xml--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!--启动级别--> <load-on-startup>1</load-on-startup> </servlet> <!-- 在SpringMVC中,/ /* 分别是什么意思: /: 只匹配所有请求,但不会去匹配jsp页面 /*: 匹配所有的请求,还有jsp页面 --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
注:spring的配置文件的位置如果不声明,默认是在/WEB-INF/下查找名为 springmvc-servlet.xml 的文件
1.3 配置springmvc.xml
<!--处理器映射器,有很多种,这个很low,但是用来讲原理很好--> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <!--处理器适配器--> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/> <!--视图解析器--> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--前缀 注意后面的斜杠必须加,不然会找不到--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean> <!--BeanNameUrlHandlerMapping这个视图解析器根据名字找bean,所以要配置一个bean--> <bean id="/hello" class="com.mxd.controller.HelloController"/>
1.4 编写控制层类和方法
//控制层要实现Controller接口,注意和注解的区分 public class HelloController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mav = new ModelAndView(); //调用业务层代码 String res ="Hello SpringMVC!";//假设返回的是这个字符串 mav.addObject("msg", res); //视图跳转 mav.setViewName("test");//跳转到/WEB-INF/jsp/test.jsp页面 return mav; } }
2、注解开发版
2.1 在pom.xml文件中添加依赖
<!--SpingMVC的依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.5.RELEASE</version> </dependency>
2.2 配置web.xml
和上面的web.xml配置一样
2.3 配置springmvc.xml
<!--1.自动扫描包,让指定包下的注解生效,由IOC容器统一管理--> <context:component-scan base-package="com.mxd.controller"/> <!--2.让Spring MVC不处理静态资源(.css .js等等) --> <mvc:default-servlet-handler /> <!--3.注册mvc注解驱动 在spring中一般采用@RequestMapping注解来完成映射关系 要想使@RequestMapping注解生效,必须向上下文中注册DefaultAnnotationHandlerMapping 和一个AnnotationMethodHandlerAdapter实例。 这两个实例分别在类级别和方法级别处理。而annotation-driven配置帮助我们自动完成上述两个实例的注入。 --> <mvc:annotation-driven /> <!--4.视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--前缀 注意后面的斜杠必须加,不然会找不到--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean>
2.4 编写控制层类和方法
@Controller//标记这个类是一个控制器 :表现层 @RequestMapping("/my")//访问路径 ,等价于url-pattern public class HelloController { @RequestMapping("/hello")//请求路径就为 /my/hello public String test1(Model model){ //向模型中添加属性msg与值,可以在JSP页面中取出并渲染 model.addAttribute("msg", "Hello SpringMVC!"); return "hello";//在/WEB-INF/jsp/下查找名为hello.jsp的页面 } }
3、可能遇到的问题
可能遇到的问题:访问出现404,排查步骤:
- 查看控制台输出,看一下是不是缺少了什么jar包。
- 如果jar包存在,显示无法输出,就在IDEA的项目发布中,添加lib依赖!
File → Project Structure → Artifacts 选择你的项目,查看WEB-INF下有没有lib目录,如果没有,新建一个lib文件夹,然后将jar包加入进去即可
- 重启Tomcat 即可解决!
- 如果在jsp中EL表达式不起作用,在头部添加:[<%@ page isELIgnored=“false” %>
三、接收请求参数
1、基本类型参数
请求的参数名称和方法的形参 同名即可
springMVC默认可以识别的日期字符串格式为: YYYY/MM/dd HH:mm:ss
通过@DateTimeFormat可以修改默认日志格式
如果请求地址中的参数名称和后端方法中的参数名称不一致时使用@RequestParam(“xxxx”)指定参数名称
@Controller public class HelloController { //处理日期格式用@DateTimeFormat()注解 @RequestMapping(value="/hello") public String test1(Model model,@RequestParam("name") String name2, int age, @DateTimeFormat(pattern = "yyyy-MM-dd") Date birth){ model.addAttribute("msg", name2+" "+age+" "+birth); return "hello"; } }
请求地址为:http://localhost:8080/hello?name=张三&age=23&birth=2021-2-2
2、实体接收参数【重点】
请求的参数名称和实体的属性 同名即可
@Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private int age; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birth; }
@RequestMapping(value = "/hello2") public String test1(User user, Model model) { System.out.println(user); model.addAttribute("msg", user); return "hello"; }
请求地址为:http://localhost:8080/hello2?name=张三&age=23&birth=2021-12-4
3、数组接收参数
请求的参数名称要和数组的名称相同
<form action="/hello3" method="post"> <input type="checkbox" name="hobby" value="足球">足球<br/> <input type="checkbox" name="hobby" value="篮球">篮球<br/> <input type="checkbox" name="hobby" value="排球">排球<br/> <input type="submit" placeholder="提交"> </form>
@RequestMapping(value = "/hello3") public String test1(String[] hobby,Model model) { System.out.println(hobby.length); model.addAttribute("msg", hobby.length); return "hello"; }
4、集合接收参数
实体类:
@Data public class UserList { List<User> userList; }
控制层的方法:
@RequestMapping(value = "/hello4") public String test1(UserList userList, Model model) { System.out.println(userList); model.addAttribute("msg", userList); return "hello"; }
请求地址为:http://localhost:8080/hello4?userList[0].name=张三&userList[0].age=22
错误解决办法:
1.在本地tomcat的解压目录 conf中的server.xml 的服务器配置:加上特殊符号的识别
URIEncoding=“utf-8”
relaxedPathChars="|{}[],%"
relaxedQueryChars="|{}[],%"<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="utf-8" relaxedPathChars="|{}[],%" relaxedQueryChars="|{}[],%"/>
2.注意:tomcat版本太高,这种传参方式:那么直接400 (当前高版本数据格式不识别)
5、路径携带参数
@PathVariable将{id}路径匹配到值赋给id参数
路径名和参数名相同则@PathVariable(“id”)可简写为 @PathVariable@RequestMapping(value = "/hello5/{name}/{age}") public String test1(@PathVariable("name") String name, @PathVariable int age, Model model) { model.addAttribute("msg", name+" "+age); return "hello"; }
请求路径为:http://localhost:8080/hello5/张三/22
6、中文乱码解决
首先,页面中字符集统一
JSP : <%@page pageEncoding="utf-8" %>
HTML : <meta charset="UTF-8">
其次,tomcat中字符集设置,对get请求中,中文参数乱码有效
Tomcat配置:URIEncoding=utf-8
示例:
<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
最后,设置此filter,对post请求中,中文参数乱码有效
<!-- 此过滤器会进行:request.setCharactorEncoding("utf-8"); --> <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>
如果还有问题,就使用下列自定义过滤器:
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Map; /** * 解决get和post请求 全部乱码的过滤器 */ public class GenericEncodingFilter implements Filter { @Override public void destroy() { } @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); } @Override public void init(FilterConfig filterConfig) throws ServletException { } } //自定义request对象,HttpServletRequest的包装类 class MyRequest extends HttpServletRequestWrapper { private HttpServletRequest request; //是否编码的标记 private boolean hasEncode; //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰 public MyRequest(HttpServletRequest request) { super(request);// super必须写 this.request = request; } // 对需要增强方法 进行覆盖 @Override public Map 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++) { try { // 处理get乱码 values[i] = new String(values[i] .getBytes("ISO-8859-1"), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } } hasEncode = true; } return parameterMap; } 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(); String[] values = parameterMap.get(name); return values; } }
然后再xml中配置这个自定义过滤器:
<filter> <filter-name>encoding</filter-name> <filter-class>com.mxd.filter.GenericEncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
四、跳转
1、转发
@RequestMapping("/forw") class ForwardController{ @RequestMapping("/test1") public String testForward(){ System.out.println("test forward1"); // 转发跳转 /views/users.jsp // return "views/users";//和下一行等价 return "forward:/views/users.jsp"; } @RequestMapping("/test2") public String testForward2(){ System.out.println("test forward2"); //转发到 /forw/test1 //return "forward:test1";//相对路径(转发到本类中的test1) //转发到 /forw/test1 return "forward:/forw/test1"; //绝对路径 } }
2、重定向
@RequestMapping("/redir") class RedirectController{ @RequestMapping("/test1") public String testRedirect1(){ System.out.println("test redirect1"); //重定向到 /redir/test1 //return "redirect:test1"; //相对路径(转发到本类中的test1) return "redirect:/redir/test1";//绝对路径 } @RequestMapping("/test2") public String testRedirect2(){ System.out.println("test redirect2"); //重定向到 /views/users.jsp return "redirect:/view/user.jsp"; } }
3、跳转细节
- 在增删改之后,为了防止请求重复提交,重定向
- 在查询之后,可以做转发
五、传值
C得到数据后,跳转到V,并向V传递数据。进而V中可以渲染数据,让用户看到含有数据的页面
转发跳转:Request作用域
重定向跳转:Session作用域
1、 Request和Session
//形参中 即可获得 request 和 session对象 @RequestMapping("/test1") public String testData(HttpSession session,HttpServletRequest req,Integer id){ session.setAttribute("user",new User()); req.setAttribute("age", 18); req.setAttribute("users",Arrays.asList(new User(),new User())); //return "test2"; return "forward:/WEB-INF/test2.jsp"; }
2、 JSP中取值
建议:重点复习 EL JSTL
//jsp中用EL表达式 取值即可 ${sessionScope.user.name} <br/> ${sessionScope.user.birth} <br> ${requestScope.age}
3、 Model
//model中的数据,会在V渲染之前,将数据复制一份给request @RequestMapping("/test") public String testData(Model model){ model.addAttribute("name", "张三"); return "index"; } //jsp中用EL表达式 取值即可 ${requestScope.name}
4、 ModelAndView
//modelandview 可以集中管理 跳转和数据 @RequestMapping("/test") public ModelAndView testData(){//返回值类型为ModelAndView //新建ModelAndView对象 ModelAndView mv = new ModelAndView(); // 设置视图名,还可以设置如何跳转(转发还是重定向) mv.setViewName("forward:/index.jsp"); // 增加数据 mv.addObject("age",18); return mv; } //jsp中用EL表达式 取值即可 ${requestScope.age}
5、ModelMap
@RequestMapping("/hello") public String hello(@RequestParam("username") String name, ModelMap model){ //封装要显示到视图中的数据 //相当于req.setAttribute("name",name); model.addAttribute("name",name); System.out.println(name); return "hello"; }
6、 @SessionAttributes
- @SessionAttributes({“gender”,“name”}) :model中的 name和gender 会存入session中
- SessionStatus 移除session
@Controller @SessionAttributes({"gender","name"}) // model中的 name和gender 会存入session中 public class UserController { @RequestMapping("/hello") public String hello(Model m){ m.addAttribute("gender",true); // 会存入session mv.addObject("name","zhj"); // 会存入session return "index"; } @RequestMapping("/hello2") public String hello(SessionStatus status){ // 移除通过SessionAttributes存入的session status.setComplete(); return "index"; } }
7、对比
Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解;
ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性;
ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转。
六、静态资源
1、 静态资源问题
静态资源:html,js文件,css文件,图片文件
静态文件没有url-pattern,所以默认是访问不到的,之所以可以访问,是因为,tomcat中有一个全局的servlet:org.apache.catalina.servlets.DefaultServlet,它的url-pattern是 “/”,是全局默认的Servlet. 所以每个项目中不能匹配的静态资源的请求,有这个Servlet来处理即可。
但,在SpringMVC中DispatcherServlet也采用了 “/” 作为url-pattern, 则项目中不会再使用全局的Serlvet,则静态资源不能完成访问。
2、 解决方案1
DispathcerServlet采用其他的url-pattern
此时,所有访问handler的路径都要以 action结尾!!
<servlet>
<servlet-name>mvc9</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>mvc9</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
3、 解决方案2
DispathcerServlet的url-pattern依然采用 “/”,但追加配置
<!--
额外的增加一个handler,且其requestMapping: "/**" 可以匹配所有请求,但是优先级最低
所以如果其他所有的handler都匹配不上,请求会转向 "/**" ,恰好,这个handler就是处理静态资源的
处理方式:将请求转会到tomcat中名为default的Servlet
-->
<mvc:default-servlet-handler/>
4、 解决方案3
- mapping是访问路径,location是静态资源存放的路径
- 将/html/** 中 /**匹配到的内容,拼接到 /hhh/后
http://…/html/a.html 访问 /hhh/a.html
<mvc:resources mapping="/html/**" location="/hhh/"/> <!--配置资源映射地址--> <mvc:resources mapping="/bootstrap/**" location="/WEB-INF/static/bootstrap/"/> <!--<mvc:resources mapping="/js/**" location="/WEB-INF/static/js/"/> <mvc:resources mapping="/css/**" location="/WEB-INF/static/css/"/> <mvc:resources mapping="/images/**" location="/WEB-INF/static/images/"/>-->
七、JSON数据的处理
1、导入依赖
<!-- Jackson springMVC默认的Json解析器就是 Jackson,所以只需要导入jackson的jar,即可使用。--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>
2、@ResponseBody
@Controller public class JsonController{ @RequestMapping("/test1") @ResponseBody //将handler的返回值,转换成json(jackson),并将json响应给客户端。 public User hello1(){ System.out.println("hello world"); User user = new User(); return user; } // @ResponseBody还可以用在handler的返回值上 @RequestMapping("/test2") public @ResponseBody List<User> hello2(){ System.out.println("hello world"); List<User> users = Arrays.asList(new User(),new User()); return users; } // 如果返回值已经是字符串,则不需要转json,直接将字符串响应给客户端 @RequestMapping(value="/test3",produces = "text/html;charset=utf-8") //produces 防止中文乱码 @ResponseBody public String hello2(){ System.out.println("hello world"); return "你好"; } }
3、@RestController
Controller类上加了@RestController注解,等价于在类中的每个方法上都加了@ResponseBody
@Controller @RestController public class JsonController{ @RequestMapping("/test1") public User hello1(){ System.out.println("hello world"); User user = new User(); return user; } //@ResponseBody还可以用在handler的返回值上 @RequestMapping("/test2") public List<User> hello2(){ System.out.println("hello world"); List<User> users = Arrays.asList(new User(),new User()); return users; } }
4、@RequestBody
@RequestBody**, 接收Json参数
实体类
@Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private int age; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birth; }
Controller控制层的方法
@RequestMapping("/json") //@RequestBody将请求体中的json数据转换为java对象 public String test1(@RequestBody User user, Model model){ System.out.println(user); model.addAttribute("msg",user ); return "test1"; }
5、测试
5.1 ajax发送json数据
第一种:
var xhr = new XMLHttpRequest(); xhr.open("post","${pageContext.request.contextPath}/json?"+new Date().getTime()); xhr.setRequestHeader("content-type","application/json");//设置请求头 xhr.send('{"name":"张三","age":"22","birth":"2021-12-16"}');//传递json串
第二种:
var user = {name:"张三",age:22,birth:"2021-12-16"}; $.ajax({ url:'${pageContext.request.contextPath}/json2/test4', type:'post', contentType:"application/json",//声明请求参数类型为 json data:JSON.stringify(user),// 转换js对象成json success:function(ret){ console.log(ret); } });
5.2 postman发送json数据
{ "name":"张三", "age":22, "birth":"2222-02-02" }
6、Jackson常用注解
6.1 日期格式化
①可以使用spring自带的注解@DateTimeFormat(pattern=“yyyy-MM-dd HH:mm:ss”),但是传入的参数必须是"2021-08-02 22:05:55",否则会抛出异常
②Jackson当中带的注解@JsonFormat(pattern=“yyyy-MM-dd HH:mm:ss”,timezone = “GMT+8”)
@Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private int age; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//必须发送json数据才能接收 private Date birth; }
6.2 属性名修改
@JsonProperty(“new_name”)
public class User{ @JsonProperty("new_id") //不再使用原属性名,而是 "new_id" private Integer id; private String name; .... get/set } 输出的json:{“new_id”:xx,"name":"xx"}
6.3 属性忽略
@JsonIgnore
public class User{ private Integer id; @JsonIgnore // 生成json时,忽略此属性 private String name; .... get/set } 输出json时: {"id":xx}
6.4 null和empty属性排除
Jackson 默认会输出null值的属性,如果不需要,可以排除。
@JsonInclude(JsonInclude.Include.NON_NULL) //null值 属性不输出
@JsonInclude(value= JsonInclude.Include.NON_EMPTY) // empty属性不输出( 空串,长度为0的集合,null值)
public class User{ private Integer id; @JsonInclude(JsonInclude.Include.NON_NULL) // 若"name==null" 忽略此属性 private String name; @JsonInclude(value= JsonInclude.Include.NON_EMPTY) // 若hobby长度为0或==null 忽略此属性 private List<String> hobby; .... get/set } 如果name=null,且 hobby长度为0,则输出json时:{"id":xx}
6.5 自定义序列化
@JsonSerialize(using = MySerializer.class) // 使用MySerializer输出某属性
public class User { private Integer id; private String name; @JsonSerialize(using = MySerializer.class) private Double salary = 10000.126;//在输出此属性时,使用MySerializer输出 .... get/set } 则输出json时:{"id":xx,"name":"xxx","salary":10000.13}
public class MySerializer extends JsonSerializer<Double> { // value即 Double salary的值 @Override public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException { // 将Double salary的值 四舍五入 String number = BigDecimal.valueOf(value).setScale(2, BigDecimal.ROUND_HALF_UP).toString(); // 输出 四舍五入后的值 gen.writeNumber(number); } }
7、 FastJson
7.1 导入依赖
<!-- FastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
7.2 安装FastJson
<mvc:annotation-driven>
<!-- 安装FastJson,转换器 -->
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<!-- 声明转换类型:json -->
<property name="supportedMediaTypes">
<list>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
7.3 使用
@ResponseBody @RequestBody @RestController 使用方法不变
7.4 常用注解
- 日期格式化:@JSONField(format=“yyyy/MM/dd”)
- 属性名修改:@JSONField(name=“birth”)
- 忽略属性:@JSONField(serialize = false)
- 包含null值:@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue) 默认会忽略所有null值,有此注解会输出null
- @JSONField(serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty) null的String输出为""
- 自定义序列化:@JSONField(serializeUsing = MySerializer2.class)
public class User implements Serializable{ @JSONField(serialize = false) private Integer id; @JSONField(name="NAME",serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty) private String name; @JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue) private String city; @JSONField(format="yyyy/MM/dd") private Date birth; @JSONField(serializeUsing = MySerializer2.class) private Double salary; ... }
public class MySerializer2 implements ObjectSerializer { @Override public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { Double value = (Double) object; // salary属性值 String text = value + "元";// 在salary后拼接 “元” serializer.write(text); // 输出拼接后的内容 } }
new User(1,null,null,new Date(),100.5); // 如上对象,转换json: {NAME:"",city:null,"birth":"2020/12/12","salary":"100.5元"}
7.5 fastjson和Jackson的区别
- Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。无依赖,不需要例外额外的jar,能够直接跑在JDK上。
- FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。
- FastJson采用独创的算法,将parse的速度提升到极致,超过所有json库。
- Jackson是当前用的比较广泛的,用来序列化和反序列化json的Java开源框架。Jackson社区相对比较活跃,更新速度也比较快, 从Github中的统计来看,Jackson是最流行的json解析器之一,Spring MVC的默认json解析器便是Jackson。
八、异常解析器
1、 现有方案,分散处理
Controller中的每个Handler自己处理异常
此种处理方案,异常处理逻辑,分散在各个handler中,不利于集中管理
public String xxx(){ try{ ... }catch(Exception1 e){ e.printStackTrace(); return "redirect:/xx/error1"; }catch(Exception2 e){ e.printStackTrace(); return "redirect:/xx/error2"; } }
2、 异常解析器,统一处理
Controller中的每个Handler不再自己处理异常,而是直接throws所有异常。
定义一个“异常解析器” 集中捕获处理 所有异常
此种方案,在集中管理异常方面,更有优势!
public class MyExResolver implements HandlerExceptionResolver{ /** * 异常解析器:主体逻辑 * 执行时刻:当handler中抛出异常时,会执行:捕获异常,并可以跳到错误页面 */ @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { ex.printStackTrace();//打印异常栈 //创建一个ModelAndView ModelAndView mv = new ModelAndView(); //识别异常 if (ex instanceof Exception1) { mv.setViewName("redirect:/xxx/error1"); }else if(ex instanceof Exception2){ mv.setViewName("redirect:/xxx/error2"); }else{ mv.setViewName("redirect:/xxx/error"); } return mv; } }
<!-- 声明异常解析器 --> <bean class="com.baizhi.exception.resolver.MyExResolver"></bean>
九、拦截器
1、 作用
作用:抽取handler中的冗余功能
2、 定义拦截器
执行顺序: preHandle–postHandle–afterCompletion
public class MyInter1 implements HandlerInterceptor{ //主要逻辑:在handler之前执行:抽取handler中的冗余代码 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("pre~~~"); /* response.sendRedirect("/springMVC_day2/index.jsp");//响应 return false;//中断请求 */ return true;//放行,后续的拦截器或handler就会执行 } //在handler之后执行:进一步的响应定制 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("post~~"); } //在页面渲染完毕之后,执行:资源回收 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("after~~"); } }
3、 配置拦截路径
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/inter/test1"/> <mvc:mapping path="/inter/test2"/> <mvc:mapping path="/inter/test*"/> <!-- test开头 --> <mvc:mapping path="/inter/**"/> <!-- /** 任意多级任意路径 --> <mvc:exclude-mapping path="/inter/a/**"/> <!--不拦截此路径--> <bean class="com.mxd.interceptor.MyInter1"></bean> <!--拦截器类--> </mvc:interceptor> </mvc:interceptors>
十、上传
1、SpringMVC的上传
1.1 导入依赖
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> <exclusions> <exclusion> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> </exclusion> </exclusions> </dependency>
1.2 页面表单代码
<%-- 表单上面必须指定一个属性:enctype="multipart/form-data" 必须有file类型的输入框:<input type="file"> --%> <form action="/upload/img" method="post" enctype="multipart/form-data"> 上传图片:<input type="file" name="filename"><br/> <input type="submit" value="上传"> </form>
1.3 在springmvc.xml中配置上传解析器
<!-- 上传解析器 id必须是:“multipartResolver” --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 最大可上传的文件大小(10485760等于10M) 单位:byte字节 超出后会抛出MaxUploadSizeExceededException异常,可以异常解析器捕获 --> <property name="maxUploadSize" value="10485760"></property> </bean>
1.4 Handler
@RequestMapping("/upload/img") public String uploadImg(@RequestParam("filename") MultipartFile multipartFile, HttpSession session) throws IOException {//必须有这个SpringMVC提供的类(本质就是流对象):MultipartFile //1.获取文件名 String filename = multipartFile.getOriginalFilename(); System.out.println(filename); //2.下一次上传图片时,图片内容换了,为了防止覆盖情况,定制唯一的UUID命名 String unique = UUID.randomUUID().toString(); System.out.println(unique); //获得文件后缀 String extension = FilenameUtils.getExtension(filename);//abc.txt txt //将生成的UUID和文件后缀拼接起来(全局唯一的文件名) String uniqueFileName = unique+"."+extension; System.out.println(uniqueFileName); //3.获取文件的类型 String type = multipartFile.getContentType(); System.out.println("type:"+type); //4.上传完成:需要将图片存入本地磁盘,放在当前web工程下的/upload_file目录下 //在webapp目录下创建一个目录"upload_file",且此目录初始不要为空,否则编译时被忽略 //如果不是在创建maven工程时同时创建的web工程,获得的目录是在项目的out目录中找 String realPath = session.getServletContext().getRealPath("upload_file"); System.out.println("realPath:"+realPath); //5.将上传的文件,存入磁盘路径中 multipartFile.transferTo(new File(realPath+"\\"+uniqueFileName)); return "upload_ok"; }
2、七牛云上传
2.1 导入依赖
<dependency> <groupId>com.qiniu</groupId> <artifactId>qiniu-java-sdk</artifactId> <version>[7.7.0, 7.7.99]</version> </dependency>
这里的
version
指定了一个版本范围,每次更新pom.xml
的时候会尝试去下载7.7.x
版本中的最新版本,你可以手动指定一个固定的版本。上面的jar包如果不管用可以直接导入以下四个依赖<dependencies> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>3.14.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.5</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.qiniu</groupId> <artifactId>happy-dns-java</artifactId> <version>0.1.6</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
2.2 页面表单代码
<form action="/qny/img" method="post" enctype="multipart/form-data"> 七牛云-上传图片:<input type="file" name="filename"><br/> <input type="submit" value="上传"> </form>
2.3 编写工具类
public class UploadUtils { //通过七牛云 ----上传图片的外链 ,返回值String public static String upload(MultipartFile multipartFile){//参数为上传的流对象 //构造一个带指定 Region 对象的配置类 //配置区域(我是华南,参数就为华南) Configuration cfg = new Configuration(Region.huanan()); //...其他参数参考类注释 UploadManager uploadManager = new UploadManager(cfg); //...生成上传凭证,然后准备上传(点击个人中心→密钥管理) String accessKey = "-AminpvyFoa2rGjt3JgPivj6DI53n6cG1aFAdlHP"; String secretKey = "DRyd_wN8O0ywa4f1OdXiH8OACTskA_kdfIm4CSRi"; String bucket = "javaee-upload-test-01";//七牛云空间的名称 //默认不指定key的情况下,以文件内容的hash值作为文件名 String key = null; //底下两行代码暂时不用 /*byte[] uploadBytes = "hello qiniu cloud".getBytes("utf-8"); ByteArrayInputStream byteInputStream=new ByteArrayInputStream(uploadBytes);*/ //身份认证 Auth auth = Auth.create(accessKey, secretKey); //生成upToken字符串,代表某个人的七牛云信息(内部利用哈希对信息进行了加密) String upToken = auth.uploadToken(bucket); try { //Response response = uploadManager.put(byteInputStream,key,upToken,null, null); //参数:(1)这里使用自己的流对象(2)key:默认为null(3)upToken字符串 Response response = null; try { response = uploadManager.put(multipartFile.getInputStream(),key,upToken,null, null); } catch (IOException e) { e.printStackTrace(); } //解析上传成功的结果 DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class); //putRet中包含身份认证的信息 System.out.println(putRet.key); System.out.println(putRet.hash); //返回的是七牛云的图片外链,可以进行查看 return "http://r5a60jn00.hn-bkt.clouddn.com/"+putRet.hash; } catch (QiniuException ex) { Response r = ex.response; System.err.println(r.toString()); try { System.err.println(r.bodyString()); } catch (QiniuException ex2) { //ignore } } return null; } }
2.4 Handler
@Controller public class QNYController { @RequestMapping("/qny/img") public String uploadFile(@RequestParam("filename") MultipartFile multipartFile, Model model){ //调用工具类 String upload = UploadUtils.upload(multipartFile); System.out.println(upload); model.addAttribute("imgUrl", upload); return "upload_ok"; } }
十一、下载
1、 超链
<a href="/download?name=abc.jpg">点击下载</a>
2、 Handler
@Controller public class DownloadController { @RequestMapping("/download") public void downloadFile(String name, HttpSession session, HttpServletResponse response) throws IOException { System.out.println("name:"+name); //获得要下载文件的绝对路径 String path = session.getServletContext().getRealPath("/upload_file"); System.out.println(path); //文件的完整路径 String real_path = path+"\\"+name; //设置响应头 告知浏览器,要以附件的形式保存内容 filename=浏览器显示的下载文件名 response.setHeader("content-disposition","attachment;filename="+name); //读取目标文件,写出给客户端 IOUtils.copy(new FileInputStream(real_path), response.getOutputStream()); //上一步,已经是响应了,所以此handler直接是void } }
十二、验证码
1、导入依赖
<!-- Kaptcha --> <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> <exclusions> <exclusion> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </exclusion> </exclusions> </dependency>
2、在web.xml中声明验证码组件
<!--验证码的配置--> <servlet> <servlet-name>cap</servlet-name> <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class> <init-param> <param-name>kaptcha.border</param-name> <param-value>no</param-value> </init-param> <init-param> <param-name>kaptcha.textproducer.char.length</param-name> <param-value>5</param-value> </init-param> <init-param> <param-name>kaptcha.textproducer.char.string</param-name> <param-value>abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789</param-value> </init-param> <init-param> <param-name>kaptcha.background.clear.to</param-name> <param-value>211,229,237</param-value> </init-param> <init-param> <!-- session.setAttribute("captcha","验证码") 存储在HttpSession名字 captcha--> <param-name>kaptcha.session.key</param-name> <param-value>captcha</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>cap</servlet-name> <url-pattern>/captcha</url-pattern> </servlet-mapping>
3、html页面
<img id="cap" src="/captcha" style="width: 85px"> <script> $(function (){ $("#cap").click(function (){ //注:this和$ (this)是两个不同的对象:this作为DOM对象只能使用javascript方法, //而$ (this)则只能使用jquery方法 //第一种刷新验证码的方式 //this.src="/captcha?time="+new Date().getTime(); //第二种刷新验证码的方式 var path =$(this).attr("src")+"?"+new Date().getTime(); $(this).attr("src",path); }) }) </script>
十三、REST
1、 开发风格
是一种开发风格,遵从此风格开发软件,符合REST风格,则RESTFUL。
两个核心要求:
- 每个资源都有唯一的标识(URL)
- 不同的行为,使用对应的http-method
访问标识 | 资源 |
---|---|
http://localhost:8989/xxx/users | 所有用户 |
http://localhost:8989/xxx/users/1 | 用户1 |
http://localhost:8989/xxx/users/1/orders | 用户1的所有订单 |
请求方式 | 标识 | 意图 |
---|---|---|
GET | http://localhost:8989/xxx/users | 查询所有用户 |
POST | http://localhost:8989/xxx/users | 在所有用户中增加一个 |
PUT | http://localhost:8989/xxx/users | 在所有用户中修改一个 |
DELETE | http://localhost:8989/xxx/users/1 | 删除用户1 |
GET | http://localhost:8989/xxx/users/1 | 查询用户1 |
GET | http://localhost:8989/xxx/users/1/orders | 查询用户1的所有订单 |
POST | http://localhost:8989/xxx/users/1/orders | 在用户1的所有订单中增加一个 |
注:一般是get查询,post添加,delete删除,put修改
2、 优点
- **输出json:
3、 使用
3.1 定义Rest风格的 Controller
@RequestMapping(value="/users",method = RequestMethod.GET)
等价
@GetMapping("/users")
@RestController public class RestController { @GetMapping("/users") public List<User> queryAllUsers(){ System.out.println("get"); List<User> users = .... return users; } @PostMapping("/users") public String addUser(@RequestBody User user){ System.out.println("Post user :"+user); return "{status:1}"; } @PutMapping("/users") public String updateUser(@RequestBody User user){ System.out.println("Put user" user:"+user); return "{status:1}"; } @GetMapping("/users/{id}") public String queryOneUser(@PathVariable Integer id){//@PathVariable 接收路径中的值 System.out.println("Get user id:"+id); return "{status:1}"; } @DeleteMapping("/users/{id}") public String deleteOneUser(@PathVariable Integer id){//@PathVariable 接收路径中的值 System.out.println("delete user id:"+id); return "{status:1}"; } }
3.2 Ajax请求
<script> function putUser(){ // 发送更新请求 (增加请求发送方式也是如此) var xhr = new XMLHttpRequest(); //定义 put,delete,get,post方式 即可,不用定义_method xhr.open("put","${pageContext.request.contextPath}/rest04/users"); // 设置请求头 xhr.setRequestHeader("content-type","application/json"); // 设置请求参数 var user = {id:1,NAME:"shine",city:"bj","birth":"2020/12/12","salary":100.5}; xhr.send(JSON.stringify(user)); xhr.onreadystatechange=function(){ if(xhr.readyState==4 && xhr.status==200){ var ret = xhr.responseText; // 解析json,并输出 console.log(JSON.parse(ret)); } } /*$.ajax({ url:'${pageContext.request.contextPath}/rest04/users', type:'put', contentType:"application/json",//声明请求参数类型为 json data:JSON.stringify(user),// 转换js对象成json success:function(ret){ console.log(JSON.parse(ret)); } });*/ } function delUser(){ // 发送删除请求 var xhr = new XMLHttpRequest(); //定义 put,delete,get,post方式 即可,不用定义_method xhr.open("delete","${pageContext.request.contextPath}/rest04/users/1"); xhr.send(); xhr.onreadystatechange=function(){ if(xhr.readyState==4 && xhr.status==200){ var ret = xhr.responseText; console.log(JSON.parse(ret)); } } } </script>
十四、跨域请求
1、 域
域:协议+IP+端口
- http://localhost:8989
- http://localhost:8080
- http://www.baidu.com:80
2、 Ajax跨域问题
- Ajax发送请求时,不允许跨域,以防用户信息泄露。
- 当Ajax跨域请求时,响应会被浏览器拦截(同源策略),并报错。即浏览器默认不允许ajax跨域得到响应内容。
- 互相信任的域之间如果需要ajax访问,(比如前后端分离项目中,前端项目和后端项目之间),则需要额外的设置才可正常请求。
3、 解决方案
- 允许其他域访问
- 在被访问方的Controller类上,添加注解
@CrossOrigin("http://localhost:8080") //允许此域发请求访问 public class SysUserController { .... }
- 携带对方cookie,使得session可用
- 在访问方,ajax中添加属性:withCredentials: true
$.ajax({ type: "POST", url: "http://localhost:8989/web/sys/login", ..., xhrFields: { // 跨域携带cookie withCredentials: true } }); 或 var xhr = new XMLHttpRequest(); // 跨域携带cookie xhr.withCredentials=true;
十五、SpringMVC的执行流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wXUtXLmb-1641542709370)(Pictures/springMVC执行流程.jpg)]
十六、SSM整合(Spring+Spring MVC+Mybatis)
1、 整合思路
此时项目中有两个工厂
- DispatcherServlet 启动的springMVC工厂==负责生产C及springMVC自己的系统组件
- ContextLoaderListener 启动的spring工厂==负责生产其他所有组件
- springMVC的工厂会被设置为spring工厂的子工厂,可以随意获取spring工厂中的组件
- 整合过程,就是累加:代码+依赖+配置。然后将service注入给controller即可
2、整合技巧
两个工厂不能有彼此侵入,即,生产的组件不能有重合。
<!-- 告知SpringMVC 哪些包中 存在 被注解的类 use-default-filters=true 凡是被 @Controller @Service @Repository注解的类,都会被扫描 use-default-filters=false 默认不扫描包内的任何类, 只扫描include-filter中指定的类 只扫描被@Controller注解的类 --> <context:component-scan base-package="com.zhj" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
<!-- 告知Spring 唯独不扫描@Controller注解的类 --> <context:component-scan base-package="com.zhj" use-default-filters="true"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
3、整合的步骤:
3.1 添加依赖
<!--依赖:junit,mybatis,mysql驱动,连接池,mybatis-spring,spring-jdbc,spring-webmvc--> <!--lombok,jackson-databind(处理json数据)等等-->
3.2 整合Spring和Mybatis
Spring的核心配置文件:spring-context.xml
<!-- 告知spring,哪些包中 有被注解的类、方法、属性 --> <!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> --> <context:component-scan base-package="com.mxd"></context:component-scan> <!-- 1、 加载properties文件中数据库参数信息 2、创建数据源对象(有好几种:①spring-jdbc中的数据源 ②mybatis中的数据源 ③druid中的数据源) 3、创建SqlSessionFactory对象 SqlSessionFactoryBean对象 4、配置mapper的扫描器对象 --> <!--1.加载properties文件中数据库参数信息(输入<property...就会有提示了)--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--2.创建数据源对象(有好几种)--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}" /> </bean> <!--3.创建SqlSessionFactory对象 SqlSessionFactoryBean对象--> <bean id="sqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean"> <!-- 配置注入的数据源--> <property name="dataSource" ref="dataSource" /> <!-- 配置mybatis映射文件的路径,如果用注解了这个就要注释--> <property name="mapperLocations" value="classpath:com/mxd/mapper/*Mapper.xml" /> <!-- 配置实体的别名包扫描--> <property name="typeAliasesPackage" value="com.mxd.pojo" /> <!--提供分页插件,如果配置了这个插件,就必须要进行分页查询,不然会抛异常--> <!--<property name="plugins"> <array> <bean class="com.github.pagehelper.PageInterceptor"></bean> </array> </property> --> </bean> <!--4.spring与mybatis整合: 配置mapper的扫描器对象 管理DAO实现类的创建,并创建DAO对象,存入工厂管理 com.mxd.mapper/dao实现对象在工厂中的id是:“首字母小写的-接口的类名” mybatis提供的这个和spring整合的依赖中MapperScannerConfigurer,Mapper的配置扫描器必须配置这个类 他的id必须mapperScannerConfigurer,否则报错 --> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer" > <!-- 必须配置扫描器数据源对象--> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> <!-- 配置接口扫描器扫描的默认包路径,然后交给spring --> <property name="basePackage" value="com.mxd.mapper"/> </bean>
3.3 SpringMVC的xml配置
<!--1.自动扫描包,让指定包下的注解生效,由IOC容器统一管理--> <context:component-scan base-package="com.mxd.controller"/> <!--2.让Spring MVC不处理静态资源(.css .js等等) --> <mvc:default-servlet-handler /> <!--3.注册mvc注解驱动 在spring中一般采用@RequestMapping注解来完成映射关系 要想使@RequestMapping注解生效,必须向上下文中注册DefaultAnnotationHandlerMapping 和一个AnnotationMethodHandlerAdapter实例。 这两个实例分别在类级别和方法级别处理。而annotation-driven配置帮助我们自动完成上述两个实例的注入。 --> <mvc:annotation-driven /> <!--4.视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--前缀 注意后面的斜杠必须加,不然会找不到--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean> <!--...拦截器,上传解析器等等--> <!--导入spring和mybatis整合的xml文件--> <import resource="spring-context.xml"/>
只需要导入其他的xml配置就整合完成
3.4 web.xml配置
注:web.xml中的配置,可以查看网上的web.xml配置的详解
<!--制定spring的配置文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </context-param> <!--过滤器--> <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> <!--监听器--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>