mvc和三层架构
MVC(开发模式)
mvc是一种开发模式,强调的是程序设计的思想
M:model模型 表示实体模型以及对数据库操作的Dao层、以及service业务层
V:view视图 进行可视化展现的一层
C:controller控制器 控制请求转发以及响应的一层 前后端进行数据交互的层级
mvc具有低耦合、代码重用性高的特点,利于项目的分工协作。但同时也会让项目架构变得较为复杂
三层架构
三层架构将整个项目划分为展示层、业务逻辑层、数据访问层。而三层架构最显著的特点就是:解耦。任何一层发生改变都不会影响到另外一层。
比如学完springMVC之后的SSM三层框架
mybatis代表数据访问层:操作数据库进行CRUD
spring实现业务逻辑层:组合dao层的方法形成复杂的业务逻辑
springMVC是展示层:接收用户参数,封装和发送数据,转发JSP页面
三层架构与mvc都具有低耦合、维护性扩展性强的特点。但是会在一定程度上降低系统的性能。
mvc与三层架构的区别:
mvc中的模型就包含了三层框架中的业务逻辑层和数据访问层。而三层架构中的展示层包含了mvc中的视图和控制器,如下图所示:
SpringMVC
springMVC是spring framework提供的web组件,是一款主流的Javaweb开发框架。同时它对应的就是三层中的controller层,主要目的就是获取客户端请求,并在后端进行操作最终响应给浏览器。
SpringMVC的一些组件:
DispatcherServlet是整个springMVC的核心组件,主要负责调度其他组件执行,所有的请求request都会首先经过DispatcherServlet再去spring-mvc.xml文件中找到对应的handler执行。
HandlerMapping处理器映射器,主要目的是解析用户需求的url,并通过url寻找对应的handler。
Handler就是servlet,也可以是我们实现了Controller接口的类
HandlerAdapter处理器适配器,使Handler按照一定规则去执行
ModelAndView模型视图,作为Handler的返回结果,返回给DispatcherServlet
ViewResolver视图解析器,DispatcherServlet通过它将最终得到的ModelAndView数据进行可视化处理,并最终渲染到浏览器
执行流程:
- DispatcherServlet接收客户端的请求
- DispatcherServlet根据请求url利用HandlerMapping来寻找对应的handler
- HandlerExecution表示具体的handler控制器,它会把解析好的信息再发送给DispatcherServlet
- DispatcherServlet找到适配器HandlerAdapter,让其去执行handler
- Handler去执行编写好的controller
- 执行controller时需要开发人员控制去操作数据业务逻辑层
- 最终Controller将会返回给HandlerAdapter结果信息再交给DispatcherServlet,比如ModelAndView(包含要响应给前端的数据,以及将要跳转的视图)
- DispatcherServlet再把获取到的视图数据交给viewResolver进行解析,将这些物理数据转换为可视化的视图
- viewResolver再将数据传给DispatcherServlet
- 由DispatcherServlet将视图数据渲染给浏览器
HelloSpringMVC
第一个使用springmvc的程序,按照上面阐述的执行流程来进行编写
- 首先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">
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 绑定spring的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 设置启动级别为1 保证DispatcherServlet与项目同时启动 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--
/ :可以接收所有请求但是不包括.jsp
/* : 接收所有请求包括.jsp
-->
<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"
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 id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean class="com.yuqu.test.ReadySpringMVC" id="/ready"/>
</beans>
- 编写controller程序
public class ReadySpringMVC implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView();
// 向视图中添加要写给用户的数据
mv.addObject("message","你好,SpringMVC");
// 设置将要跳转的视图 设置好后该字段将会在视图解析器中进行补全
// ready---> ready.jsp
mv.setViewName("ready");
return mv;
}
}
最后编写jsp进行测试:
<body>
${message}
</body>
简单分析一波理解:首先用户提交请求后,请求先走DispatcherServlet也就是请求分发器,分发器根据将请求交给HandlerMapping映射器,映射器根据请求url来匹配对应的Handler,匹配到之后交给适配器,适配器操控Handler来执行已经编写好的controller。controller最终会返回一个结果,比如视图模型数据,该数据包含了想要展示给用户的数据以及将要跳转的视图界面。controller将结果提交HandlerAdapter适配器,适配器在交给分发器。分发器将视图模型数据交给视图解析器进行解析,解析完毕后再次回到分发器,最终渲染到浏览器视图界面中
使用注解开发SpringMVC
使用注解开发,首先编写web.xml文件,注册DispatcherServlet。
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 绑定spring配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!-- 设置启动级别与项目同时启动 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
这个其实是写定的,只是有的时候spring核心配置文件名不同需要修改,每次使用的时候直接拿来就可以。然后为spring打开注解支持,首先我们要在头文件进行不成context和mvc,之后继续打开注解的支持、扫描包、资源过滤、以及开启自动注入的映射器HandlerMapping和HandlerAdapter适配器,最后仍然需要手动的去配置视图解析器,如下
<?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="com.yuqu.controller"/>
<!-- 默认过滤静态资源 -->
<mvc:default-servlet-handler/>
<!-- 自动开启和注入HandlerMapping和HandlerAdapter -->
<mvc:annotation-driven/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="suffix" value=".jsp"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
</bean>
</beans>
springmvc的配置文件其实也是可以给定的,只是当项目包名发生变化时修改一下扫描的路径即可。注解开发就是针对Controller的优化
@Controller
@RequestMapping("/hello")
public class HelloAnnoController {
// localhost:8080/../hello/h1
@RequestMapping("/h1")
public String hello(Model model){
// 封装数据
model.addAttribute("message","HELLO ANNOCONTROLLER!");
// 返回的是视图名会被视图解析器处理
return "hello";
}
}
@RequestMapping中我们可以写出请求的路径,以及请求的方式get、post等等
或者可以直接使用更加简便的注解
@PathVariable:RestFul风格再url地址栏获取参数时可以配置方法的参数映射
@RequestMapping(value = "/login/{a}")
public String testForward(@PathVariable int a, Model model){
@GetMapping:完全等同于@RequestMapping( method = RequestMethod.GET )
@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping
@RestController:不使用视图解析器与@Controller相似
@ResponseBody:所修饰的方法返回的字符串不再被视图解析器解析 仅仅是返回一个字符串
@RequestParam:表示该参数是接收前端的参数,可以为其匹配的字段起别名。前端中的参数名则必须与该别名一致
转发和重定向
首先是在不配置视图解析器的情况下,设置转发和重定向
@RequestMapping(value = "/login")
public String testForward(Model model){
model.addAttribute("message","Hello,2QEW!");
// 转发
return "/WEB-INF/jsp/hello.jsp";// 需要使用全限定名
}
@RequestMapping(value = "/red")
public String testRedirect(Model model){
model.addAttribute("message","Hello,3GFV!");
// 重定向 数据无法发送到前端
return "redirect:/index.jsp";
}
在重定向的过程中发现了一个问题,重定向时,model中所存储的数据会被清空,视图将会无法获取,但是转发不会。配置了视图解析器之后的转发和重定向使用方式类似,如下:
@RequestMapping(value = "/login")
public String testForward(Model model){
model.addAttribute("message","Hello,2QEW!");
// 转发
return "/hello";// 不需要再使用全限定名
}
@RequestMapping(value = "/red")
public String testRedirect(Model model){
model.addAttribute("message","Hello,3GFV!");
// 重定向 数据无法发送到前端
return "redirect:/index.jsp";
}
前端数据回显
接收前端请求中的参数,并进行显式。首先来看如果接收的是一个字段的情况
@Controller
@RequestMapping(value = "/c3")
public class Controller3 {
@RequestMapping(value = "/t1")
public String test1(@RequestParam("username") String name, Model model){
System.out.println("接收到前端的参数回显name="+name);
model.addAttribute("message",name);
return "hello";
}
}
@RequestParam:表示该参数是接收前端的参数,可以为其匹配的字段起别名。前端中的参数名则必须与该别名一致
下面看如果传的是一个对象
@RequestMapping(value = "/t2")
public String test2(User user){
System.out.println(user);
return "hello";
}
我们可以直接将对象的类型作为形参去接收前端传回的对象数据,但是必须保证对象中的各属性字段与请求中的字段名一致,否则传回则为null
传参格式:http://localhost:8080/c3/t2?id=1&name=张三&age=13
解决乱码
配置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>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;
}
}
关于Jackson
SpringMVC解决json格式的乱码问题
固定写法,拷贝使用
<!-- Spring配置解决json格式乱码问题 固定写法 -->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
Jackson获取工具类
public static String getJson(Object obj) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(obj);
}
public static String getJson(Object obj,String dateForm) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SimpleDateFormat dateFormat = new SimpleDateFormat(dateForm);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);
mapper.setDateFormat(dateFormat);
return mapper.writeValueAsString(obj);
}