SpringMVC 是由Spring提供的,建立在 Servlet API 之上的 Web 框架
三层架构
系统标准的三层架构包括:表现层、业务层、持久层。 表现层:
web层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http协议请求web 层,web 需要接收 http 请求,完成 http 响应。
业务层:
service 层。它负责业务逻辑处理。web 层依赖业务层,但是业务层不依赖 web 层。业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说的,事务应该放到业务层来控制)
持久层:
dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库中。
MVC
MVC是是模型(model)-视图(view)-控制器(controller)的缩写,MVC要实现的目标是将软件用户界面和业务逻辑分离以使代码可扩展性、可复用性、可维护性、灵活性加强。
- model(模型):Model层是业务模型,业务模型进行业务处理,返回数据
- View(视图):View层是界面。通常指的就是 jsp 或者 html。作用一般就是展示数据
- Controller(控制器):Controller层用来调度View层和Model层,控制器只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。本身不输出任何东西和做任何处理。所以Controller中的内容能少则少,这样才能提供最大的灵活性。 三层架构的意义在于各层解耦,各司其职
MVC设计模式在于解耦 View 和 Model
SpringMVC配置文件
<!--注解扫描-->
<context:component-scan base-package="com.eponine.mvc"></context:component-scan>
<!--配置mvc注解支持: 例如json解析注解-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--视图解析器-->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
</bean>
web.xml
<!--mvc核心:前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置servlet初始化参数 读取springmvc配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!--配置servlet启动优先级,数值越低优先级越高-->
<load-on-startup>5</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--拦截以/开头的请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
controller
@Controller
public class HelloController {
//url http://localhost:8080/hello
@RequestMapping("/hello")
public String hello(){
System.out.println("Hello");
return "/WEB-INF/pages/success.jsp";
}
}
SpringMVC执行流程
web.xml配置了前端控制器DispatcherServlet
- DispatcherServlet被加载时,调用servlet生命周期的init()方法,配置初始化(IOC容器、HandlerMapping、HandlerAdapter、ViewResolver 等等)
配置初始化完毕,HandlerMapping中已经保存了mapping(请求URL)对应的handlerMethod(处理方法)
每次请求到来,会调用DispathcerServlet.doDispatch()方法处理
dispatchServlet 根据请求request,调用处理器映射器(HandlerMapping),获取到对应的 handler
dispatchServlet 根据handler, 获取对应的处理器适配器(HandlerAdapter)
由 HandlerAdapter 执行 handler(目标方法),返回ModelAndView
dispatchServlet处理结果: this.processDispatchResult(...)
通过ModelAndView获取到逻辑视图viewName("/WEB-INF/pages/success.jsp")
调用视图解析器 (ViewResolver) 根据视图名viewName解析,得到View对象
由view对象进行结果渲染:
- 将数据保存在request域中,
- 转发到对应页面
源码级别的流程分析:
-
当我们配置了
<load-on-startup>5</load-on-startup>会在容器启动的时候创建DispatchServlet -
完成DispatchServlet的创建会调用initStrategies()方法,完成springMVC核心9大组件的初始化
-
在这里会初始化 处理器映射器、处理器适配器、视图解析器、等等
-
this.initHandlerMappings(context); this.initHandlerAdapters(context); this.initViewResolvers(context); -
怎么初始化的?
- 先根据类型去IOC容器中找handlerMapping ,如果有handlerMapping 的定义信息,就创建对象放入IOC容器
- 如果第一步没有根据类型找到,再根据名称+类型找定义信息,如果有创建对象
- 如果前两步都没有完成对象的创建,就读取默认配置DispatcherServlet.properties创建对象放入到IOC容器
-
-
-
完成初始化之后,就是等待请求来,每个被拦截到的请求都会被doDispatch()方法处理
-
先根据mapping(/hello)找到HandlerMapping,就是由哪个handlerMethod处理请求
-
在根据刚才的handler找到由哪个处理器适配器可以处理这个方法
-
由处理器适配器去执行handlerMothod,返回ModelAndView对象
-
由 DispatcherServlet调用processDispatchResult()处理结果
-
怎么处理?
-
先根据ModelAndView获取viewName
-
调用视图解析器解析viewName,返回view对象
-
通过view对象完成结果处理view.render(mv.getModelInternal(), request, response);
- 第一步:把数据存入到request域
- 第二步:转发到页面,页面就可以从request域获取数据
-
SpringMVC的核心组件
- HandlerMapping (处理器映射器) 负责根据请求找到相对应的处理器(我们自己写的Controller)
- HandlerAdapter(处理器适配器) 负责controller方法调用
- ViewReslover(视图解析器) 负责解析处理ModelAndView,得到物理视图View对象
@RequestMapping
@RequestMapping 注解用来进行URL和方法的绑定。可以通过它的属性对请求做出各种限制,常用选项有:
- value:用来指定请求URL(和path作用一样)
- method:用来限定请求方式类型
- params:用来限定请求必须传递哪些参数 上面的选项可以单独使用,也可以组合使用,组合使用时,必须同时满足才行。
@RequestMapping 可以写在方法上,表示对该方法限制,也可以写在类上,表示对该类中所有方法进行限制
value属性
@Controller
public class HelloController {
//url http://localhost:8080/hello?username=hawk&age=18
@RequestMapping(value = "/hello",
method = RequestMethod.GET,
params = {"username","age"})
public String hello() {
System.out.println("Hello");
return "success";
}
}
SpringMVC参数接收
在SpringMVC中可以使用多种类型来接收前端传入的参数
- 简单类型:基本类型的包装类型、String
- 对象类型
- 数组类型
- 集合类型
简单类型参数
保证前台传递的参数名和方法中的形参名称一致 如果没有传递参数,会将null赋值给参数,如果参数是基本类型,会出现异常
//url http://localhost:8080/hello1?name=hawk&age=18
@RequestMapping("/hello1")
public String hello1(String name,Integer age){
System.out.println(name+":"+age);
return "success";
}
对象类型参数
保证传递的参数和对象属性名称一致(SpringMVC调用对象属性对应的set方法进行参数封装)
//url http://localhost:8080/hello2?name=hawk&age=18
@RequestMapping("/hello2")
public String hello2(User user){
System.out.println(user);
return "success";
}
数组类型参数
保证参数名和方法形参名称一致,可以使用数组类型接收,也可以使用字符串接收
//url http://localhost:8080/hello3?name=hawk&name=dover
@RequestMapping("/hello3")
public String hello3(String[] name){
for (String thisName : name) {
System.out.println(thisName);
}
return "success";
}
//url http://localhost:8080/hello3?name=hawk&name=dover
@RequestMapping("/hello3")
public String hello3(String name){
System.out.println(name);
return "success";
}
集合类型参数
获取集合参数时,需要借助注解 @RequestParam
//url http://localhost:8080/hello4?name=hawk&name=dover
@RequestMapping("/hello4")
public String hello4(@RequestParam ArrayList<String> name){
for (String s : name) {
System.out.println(s);
}
return "success";
}
中文乱码处理
我们可以使用过滤器对请求进行拦截,设置编码,解决中文乱码问题,SpringMVC已经为我们封装好了乱码处理的过滤器,只需要在web.xml中配置,指定需要的编码即可.
<!--中文乱码处理过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--指定编码格式-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
自定义类型转换器
接收日期类型参数
//url http://localhost:8080/hello5?date=1998/01/02
@RequestMapping("/hello5")
public String hello5(Date date){
System.out.println(date);
return "success";
}
如果把传递的日期格式改为1998-1-2后台出现转换异常:
ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '1998-1-2';
自定义类型转换器步骤如下:
- 实现Converter接口,定义类型转换器类
- 配置,将自定义类型转换器加入到类型转换器集合中
public class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String s) {
//滤空
if(s==null){
return null;
}
//首先以yyyy/MM/dd格式处理
try{
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
Date parse = simpleDateFormat.parse(s);
return parse;
}catch (Exception e){
//出现异常以yyyy-MM-dd格式处理
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date parse = null;
try {
parse = simpleDateFormat.parse(s);
return parse;
} catch (ParseException ex) {
//格式不正确
ex.printStackTrace();
return null;
}
}
}
}
<!--配置mvc注解支持: 例如json解析注解-->
<mvc:annotation-driven conversion-service="dateConverter"></mvc:annotation-driven>
<!--配置自定义类型转换器:第一步-->
<bean id="dateConverter"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.eponine.mvc.converter.DateConverter"></bean>
</set>
</property>
</bean>
文件上传
单文件上传
- 配置文件上传的解析器
<!--文件上传-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--单次上传文件大小限制 单位字节,10M-->
<property name="maxUploadSize" value="10485760"></property>
</bean>
- 完成上传表单
<form action="/hello6" method="post" enctype="multipart/form-data">
<input type="file" name="file" multiple> <br/>
<input type="submit" value="上传">
</form>
- 后台接收并保存上传文件
@RequestMapping("/hello6")
public String hello6(MultipartFile file){
String filename = file.getOriginalFilename();
File realFile = new File("E:/file/" + filename);
try {
file.transferTo(realFile);
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
多文件上传
@RequestMapping("/hello7")
public String hello7(MultipartFile[] file){
for (MultipartFile multipartFile : file) {
String filename = multipartFile.getOriginalFilename();
File realFile = new File("E:/file/" + filename);
try {
multipartFile.transferTo(realFile);
} catch (IOException e) {
e.printStackTrace();
}
}
return "success";
}
常用注解
@RequestParam
@RequestParam标注在方法参数之前,用于对传入的参数做一些限制,支持三个属性:
- value:默认属性,指定接收哪个参数的值,默认接收和方法参数同名的请求参数
- required:请求中是否必须有该参数,如果设置了默认值,请求中可以没有该参数
- defaultValue:默认值,如果传递参数将覆盖默认值
多个属性可以组合使用,该注解还可用于接收集合类型参数
//url http://localhost:8080/hello1?name=hawk&age=18
@RequestMapping("/hello1")
public String hello1(@RequestParam(value = "name") String userName, Integer age,
@RequestParam(required = false,defaultValue = "123456")String password){
System.out.println(userName+":"+age+":"+password);
return "success";
}
@RequestHeader
用于接收HTTP请求头信息
@RequestHeader(key) 指定接收单个的header信息
@CookieValue
用于接收指定cookie的value值
释放静态资源
前端控制器(DispatchServlet) 配置的 ==/== ,会导致静态资源(js/html/jpeg...)都会被拦截,进行路径匹配寻找对应的handler进行处理
解决方案1
在spring-mvc.xml中配置默认的servlet处理
<!--当映射器找不到对应Url的mapping时,先不要报错,而是交给tomcat默认的servlet处理-->
<mvc:default-servlet-handler/>
解决方案2
在web.xml中配置固定的目录匹配规则,静态资料的路径不要与该路径开头即可,该方式适合新建项目使用,否则中途修改,需要改大量mapping
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--方式一:增加固定前缀,这样静态资源就不会被拦截-->
<url-pattern>/eponine/*</url-pattern>
</servlet-mapping>