SpringMVC就是Spring的一个模块,它专用来做web开发。
SpringMVC项目必须有的一个servlet对象:DispatcherServlet(中央调度器,继承自HttpServlet,它也被称为【Front Controller】),它负责【接收】用户所有的请求,并将该请求【转发】给与该请求对应的被我们定义@controller注解的对象进行处理,所有controllers的处理结果会返回给中央调度器,中央调度器会将这些结果转发给View,View将结果数据添加到response报文中,最后由中央调度器将response报文回应给用户。
1. 开发步骤
先把依赖加好。
1.1 配置文件
在web服务器启动后就要创建DispatcherServlet对象的实例,因为该对象实例化(调用servlet对象的init()方法)过程中会创建springmvc容器对象,并读取springmvc配置文件,将该配置文件中所有的bean对象都创建好并放到全局作用域中。
springmvc创建容器对象时,读取的配置文件默认是/WEB-INF/<servlet-name>-servlet.xml
,不太灵活,因此一般都会自定义springmvc配置文件的路径。
在web.xml中配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </init-param>
<load-on-startup>1</load-on-startup> </servlet>
|
1.2 配置mapping
也是在web.xml中配置,设置对哪些资源发起的请求交给哪个中央调度器处理
1 2 3 4
| <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
|
代表所有对以.do结尾的资源发起的请求都会转发给上一小节定义的中央调度器servlet。
1.2.1 url pattern详细
*.do
代表的是所有动态文件,<url-pattern>*.do</url-pattern>
代表中央调度器只处理所有对动态资源的请求,对静态资源的请求依然由web服务器来完成(web服务器比如tomcat可以处理对静态资源的请求)。
如果更改为<url-pattern>/</url-pattern>
,这就会使tomcat能够处理静态资源请求的能力【失效】,客户端对所有资源文件的请求都会被发送到中央调度器,如果采用了这种配置,也不需要区分动静态资源了(是否加后缀do)。
【让动静态资源请求都经由中央调度器处理的配置方法】
在spring配置文件中加入<mvc:default-servlet-handler/>
为什么不让tomcat处理静态资源文件?
1.2.2 另外一种处理静态资源请求的方式(主要)
在spring的配置文件中加入
1 2 3 4 5
|
<mvc:resources mapping="/staticSource/**" location="/localStaticSource/"/>
|
配置完成后框架会创建ResourceHttpRequestHandler来处理所有对localStaticSource目录下静态资源的请求。
1.3 创建controller类
在类的上面用@Controller注解
@RequestMapping可以放在类上,也可以放在方法上。
在方法上面用@RequestMapping注解,它的作用是将一个请求地址和一个方法绑定在一起,代表所有对某请求地址发起的请求都由该被注解的方法处理,它属性中的value为String类型,表示请求的url地址,该值必须唯一,一般以"/"
开头。另外,被注解的方法有返回值,类型为ModelAndView。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Controller public class MyController {
@RequestMapping(value = {"/somePage1.do", "somePage2.do"}) public ModelAndView func(){ service();
ModelAndView mav = new ModelAndView(); mav.addObject("msg","hello springmvc");
mav.setViewName("/show.jsp");
return mav; } }
|
在类上面用@RequestMapping注解,代表该类中用@RequestMapping注解的请求地址所属的公共模块,比如在类上注解了@RequestMapping("/user")
,那么该类中方法上的注解@RequestMapping(value = {"/somePage1.do", "somePage2.do"})
中somePage1.do和somPage2.do就都属于/user目录下的了。
1.3.1 指定浏览器提交数据的方式
通过配置@RequestMapping的method参数可以指定当浏览器以何种方式提交数据时用被注解的方法进行处理。
@RequestMapping(value = "/somePage.do", method = RequestMethod.POST)
即用户浏览器向somePage.do发起请求时,只有当请求是以post方法提交时才会调用被注解的函数对请求进行处理。
1.3.2 获取request、response和session对象
它们会作为形参传入被@RequestMapping注解的方法。
1 2 3 4 5 6 7 8
| @RequestMapping(value = "/somePage.do", method = RequestMethod.POST) public ModelAndView func(HttpServletRequest request, HttpServletResponse response){
ModelAndView mav = new ModelAndView(); mav.addObject("msg",request.getParameter("user")); mav.setViewName("/show.jsp"); return mav; }
|
1.3.3 获取用户提交的参数
【逐个接收】
用户提交的key会作为形参传入被@RequestMapping注解的方法(key的名称必须一致)
比如用户提交了name=tom&age=12
我们可以通过定义方法参数获取这些数据,注意参数名必须和用户提交数据的key名称一致
1 2 3 4 5 6 7 8 9 10
| @RequestMapping(value = "/somePage.do", method = RequestMethod.POST) public ModelAndView func(String name, int age){ ModelAndView mav = new ModelAndView(); mav.addObject("myName",name); mav.addObject("myAge",age);
mav.setViewName("/show.jsp");
return mav; }
|
这里会碰到两个问题:
- 用户输入的int类型参数为空时,报400错误
框架内部实现上述结果的原理是:先调用request.getParameter("name")
获取到name对应的值(String类型),然后调用其valueOf方法将其转为我们定义的被@RequestMapping注解的方法的参数类型。本例中age的值会被转为int,那如果用户输入的age为空呢?就会出现400错误,因为空字符串不能被转为整数。因此,方法参数必须用【包装类型】,【本例中int应该改为Integer】来避免异常。
这种接收方法的好处:如果用户传入的参数类型不对,那么绑定的方法根本就不会执行,减少我们判断输入合法性的代码。
- post接收中文乱码
用过滤器处理,框架给我们提供了一个CharacterEncodingFilter,去web.xml下加入如下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <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> <init-param> <param-name>forceRequestEncoding</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
【对象接收】
对象的属性名必须与客户端提交的key名称相等,框架会自动创建对象并调用set方法为对象赋值。
假如对象定义如下,get和set方法已写好
1 2 3
| public class Student { private String name; private Integer age;
|
被@RequestMapping注解的方法的参数列表中加入Student类型参数,框架会自动帮我们创建Student类型对象并将客户端传来的值赋给Student对应的属性。
1 2 3 4 5 6 7 8 9
| @RequestMapping(value = "/somePage.do") public ModelAndView func(Student student){ ModelAndView mav = new ModelAndView(); mav.addObject("name",student.toString());
mav.setViewName("/show.jsp");
return mav; }
|
1.3.4 返回值类型
前面一直用ModelAndView做返回值,这是当请求处理后有数据产生,并且要做页面跳转时用的。如果请求处理后无数据产生(仅做页面跳转),或不需要做页面跳转,用ModelAndView就不合适了。
1.3.4.1 String
- 返回值String代表页面的名称,可直接跳转到对应页面。
1 2 3 4 5 6
| @RequestMapping(value = "/somePage.do") public String func(HttpServletRequest request, HttpServletResponse response){ request.setAttribute("name", "tom");
return "/show.jsp"; }
|
- 返回值String是数据
区分String代表页面还是数据,就看方法有没有被@ResponseBody注解(在1.3.4.3中介绍),如果有就是数据,否则就是页面。
1.3.4.2 void(了解)
处理ajax时会使用void返回值,通过把数据写入HttpServletResponse响应ajax请求(ajax只请求数据,与视图无关)。这时写法与之前学习的servlet相同。
该方法的缺点在于其响应给客户端的数据不是json格式,如果要将其改为json格式又需要很多步骤,因此框架帮我们封装了将java数据改为json格式的操作。
1.3.4.3 Object
用Object返回可以解决void返回值的缺点,我们可以直接返回json类型的数据。
加jackson依赖
开启注解驱动,在spring配置文件中加入(注意看好右侧选择网址以mvc结尾的那个)
1
| <mvc:annotation-driven/>
|
- 在需要返回json类型的方法上用@ResponseBody注解
1 2 3 4 5 6
| @RequestMapping(value = "/somePage.do") @ResponseBody public Student returnJson(String name, Integer age){ Student stu = new Student("tom", 18); return stu; }
|
上面是返回一个json对象,当然也可以返回一个json类型的list。回忆之前servlet中返回一个json类型数据的繁琐步骤,现在可以一步完成。
1.4 配置视图解析器
通过mav.setViewName("/WEB-INF/a/b/c/d/show.jsp");
可以将视图填充到ModelAndView中,但是如果视图所在目录很深呢?不用每次都写全路径。
在springmvc的配置文件中添加:
1 2 3 4 5 6 7 8
| <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/a/b/c/d/(注意前后都有斜杠)"/> <property name="suffix" value=".jsp"/> </bean>
|
现在指定路径就很方便了:mav.setViewName("show");
2. SSM整合开发
Spring负责业务层工作,它作为一个容器管理service、dao以及工具类对象。
SpringMVC负责视图层工作,接收请求以及显示处理结果,它作为Spring的子容器管理所有controller对象。SpringMVC和Spring类似于继承关系,SpringMVC这个子类可以访问其父类Spring中的对象,这样就可以在controller对象中调用service对象执行业务层逻辑。
MyBatis负责持久层工作,访问数据库。
【开发步骤】
2.1. 准备工作
- 创建好项目数据库
- 创建好包,dao、controller、domain和service等
2.2. 导入依赖并配置配置文件扫描路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| <dependencies>
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.1</version> </dependency>
</dependencies>
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency>
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.4.1</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.3</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency>
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.3</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.3</version> </dependency>
</dependencies>
<build>
<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*</include> </includes> <filtering>false</filtering> </resource> </resources>
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.0</version> </plugin> </plugins> </build>
|
2.3. springmvc配置文件
在resources下创建conf目录,之后所有配置文件都放这里面。
springmvc配置文件习惯取名为dispatcherServlet.xml。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?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:aop="http://www.springframework.org/schema/aop" 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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.aaron.controller"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:annotation-driven/>
</beans>
|
2.4. spring配置文件
也放在conf目录下,一般取名为applicationContext.xml。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <?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:aop="http://www.springframework.org/schema/aop" 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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.aaron.service"/>
<aop:aspectj-autoproxy/>
<context:property-placeholder location="classpath:conf/jdbc.properties"/>
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <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>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="myDataSource"/>
<property name="configLocation" value="classpath:conf/mybatis.xml"/> </bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.aaron.dao"/> </bean>
</beans>
|
2.5. mybatis配置文件
在conf下创建mybatis.xml。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases> <package name="com.aaron.domain"/> </typeAliases>
<mappers> <package name="com.aaron.dao"/> </mappers> </configuration>
|
2.6. web.xml配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <?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>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:conf/dispatcherServlet.xml</param-value> </init-param>
<load-on-startup>1</load-on-startup> </servlet>
<servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:conf/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
<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>
</web-app>
|
3. 转发和重定向
forward和redirect都是关键字,它们都与视图解析器无关。
3.1 forward
当我们想要跳转的页面不在视图解析器的作用范围内,就可以用forward显示转发。
setViewName默认是通过forward完成页面跳转的。
1 2 3 4 5 6 7 8 9 10 11 12
| @RequestMapping("/addStudent.do") public ModelAndView addStudent(Student student){ String feedback = "regitser failed"; int num = service.addStudent(student); if(num>0) feedback = student.getS_name()+" register succeed!";
ModelAndView mv = new ModelAndView(); mv.addObject("feedback", feedback); mv.setViewName("forward:/WEB-INF/someDir/somePage.jsp");
return mv; }
|
3.2 redirect
1 2 3 4 5 6 7 8
| @RequestMapping("/testRedirect.do") public ModelAndView testRedirect(int num){ ModelAndView mv = new ModelAndView(); mv.addObject("feedback", num); mv.setViewName("redirect:/result.jsp");
return mv; }
|
现在前端要通过${param.feedback}
来取值,用${feedback}
取不到。
重定向不能访问WEB-INF下的资源
4. 异常处理
不同方法中经常要处理相同的异常,而且一些情况下异常代码可能比本身的业务逻辑代码要多的多,很杂乱。springmvc可以帮我们处理异常,现在我们只需要把异常抛给框架即可。
springmvc采用统一,全局的异常处理,把controller中所有异常处理都集中到一个地方,其实现方法就是利用AOP来解耦合。
【步骤】
controller发生的异常统统抛出。
4.1 创建一个全局异常处理类
在类的上面加入@ControllerAdvice
注解。
在类中的方法上加入@ExceptionHandler
注解。
4.2 创建异常发生时要跳转的页面
4.3 springmvc配置文件
新增一个组件扫描器扫描@ControllerAdvice所在的包名。
5. 拦截器
拦截器和过滤器的区别在于:过滤器用来过滤请求参数,设置编码字符集等工作;拦截器拦截用户的请求,并对其做判断处理。
拦截器是全局的,一个项目中可以有多个拦截器,一个拦截器可以同时拦截多个controller接收到的请求。拦截器通常用在用于登陆处理、权限检查和记录日志等。
【拦截器的执行时间】
在controller的方法执行之前、之后以及请求处理完成后,即拦截器功能可以在三个不同的时机执行,可自行选择。
【拦截器的使用步骤】
- 定义类,实现HandlerInterceptor接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class MyInterception implements HandlerInterceptor {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return false; }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
} }
|
- 在springmvc配置文件中声明拦截器
1 2 3 4 5 6 7 8 9 10
| <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/user/**"/> <bean class="com.aaron.handler.MyInterception"/> </mvc:interceptor> </mvc:interceptors>
|
5.1 拦截器和过滤器的区别
- 过滤器是servlet对象,拦截器是框架中的对象
- 过滤器实现Filter接口,拦截器实现HandlerInterceptor接口
- 过滤器用来设置request和response参数,侧重于数据过滤;拦截器用来验证请求。
- 过滤器先于拦截器执行
- 过滤器对象由tomcat创建,拦截器对象由springmvc容器创建
- 过滤器只有一个执行时机,拦截器有三个
- 过滤器处理静态资源,拦截器侧重拦截对动态资源发起的请求(只要请求可以被DispatcherServlet接收,就可以配置使得其被拦截器拦截)