1.DispatcherServlet中的映射路径
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
1)拦截所有请求
此处需要特别强调的是 <url-pattern>/</url-pattern>使用的是/,而不是/*,如果使用/*,那么请求时可以通过DispatcherServlet转发到相应的Controller中,但是Controller返回的时候,如返回的jsp还会再次被拦截,这样导致404错误,即访问不到jsp。
‘
2)自定义拦截请求
拦截*.do、*.html、*.action, 例如/user/add.do
这是最传统的方式,最简单也最实用。不会导致静态文件(jpg,js,css)被拦截。
拦截/,例如:/user/add
可以实现REST风格的访问
弊端:会导致静态文件(jpg,js,css)被拦截后不能正常显示。
拦截/*,这是一个错误的方式,请求可以走到Controller中,但跳转到jsp时再次被拦截,不能访问到jsp。
3)静态资源的访问,如jpg,js,css
如果DispatcherServlet拦截"*.do"这样的有后缀的URL,就不存在访问不到静态资源的问题。
如果DispatcherServlet拦截"/",为了实现REST风格,拦截了所有的请求,那么同时对*.js,*.jpg等静态文件的访问也就被拦截了。
例如:
<link href="css/hello.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="js/hello.js"></script>
<img alt="none" src="images/logo.png">
解决方式一:利用Tomcat的defaultServlet来处理静态文件
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
特点:1.要配置多个,每种文件配置一个。
2.要写在DispatcherServlet的前面(和tomcat版本有关),让defaultServlet先拦截请求, 这样请求就不会进入Spring了
3. 高性能。
解决方式二: 使用<mvc:resources>标签,例如:
<mvc:resources mapping="/images/**" location="/images/"/>
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
mapping: 映射
两个*,表示映射指定路径下所有的URL,包括子路径
location:本地资源路径
这样如果有访问/images或者/js或者/css路径下面的资源的时候,spring就不会拦截了
解决方式三: 使用<mvc:default-servlet-handler/>标签
在spring配置文件中加入此标签配置即可
2.spring提供的编码过滤器
查看这个过滤器类源码便可知这里所传的俩个参数的作用
<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>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.Controller接口及其实现类
Controller是控制器接口,此处只有一个方法handleRequest,用于进行请求的功能处理,处理完请求后返回ModelAndView(Model模型数据部分 和 View视图部分)。
如果想直接在处理器/控制器里使用response向客户端写回数据,可以通过返回null来告诉DispatcherServlet我们已经写出响应了,不需要它进行视图解析
Spring默认提供了一些Controller接口的实现以方便我们使用,在Eclipse中选择Controller接口然后右键open type Hierarchy即可查看改接口的实现类,每个实现类都有自己特殊的功能,这里以实现类AbstractController为例简单介绍下。
查看AbstractController类中代码可知,我们写一个Controller的时候可以继承AbstractController然后实现handleRequestInternal方法即可。
提供了【可选】的会话的串行化访问功能,例如:
//即同一会话,线程同步
public class HelloWorldController extends AbstractController{
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
String name = request.getParameter("name");
//ModelAndView对象中包括了要返回的逻辑视图,以及数据模型
ModelAndView mv = new ModelAndView();
//设置视图名称,可以是字符串 也可以是视图对象
mv.setViewName("hello");
//设置数据模型
mv.addObject("name", name);
return mv;
}
}
<bean name="/hello" class="com.briup.web.controller.HelloWorldController">
<property name="synchronizeOnSession" value="true"></property>
</bean>
直接通过response写响应,例如:
public class HelloWorldController extends AbstractController{
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.getWriter().write("Hello World!!");
//如果想直接在该处理器/控制器写响应 可以通过返回null告诉DispatcherServlet自己已经写出响应了,不需要它进行视图解析
return null;
}
}
强制请求方法类型,例如:
//只支持post和get请求
<bean name="/hello" class="com.briup.web.controller.HelloWorldController">
<property name="supportedMethods" value="POST,GET"></property>
</bean>
当前请求的session前置条件检查,如果当前请求无session将抛出HttpSessionRequiredException异常,例如:
//在进入该控制器时,一定要有session存在,否则抛出HttpSessionRequiredException异常。
<bean name="/hello" class="com.briup.web.controller.HelloWorldController">
<property name="requireSession" value="true"/>
</bean>
4.自定义适配器
一般情况下,springMVCSimpleControllerHandlerAdapter会是我们常用的适配器,也是SpringMVC中默认的适配器,该适配器中的主要代码如下:
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
}
从代码中可以看出,它首先会判断我们的handler是否实现了Controller接口,如果实现了,那么会调用Controller接口中的handleRequest方法
那么根据这种方式能看出,我们也可以有自己的适配器的实现,
那么就可以让任意类成为SpringMVC中的handler了,
无论我们的类是否实现了Controller接口
例如:
自己的接口:
public interface MyController {
public ModelAndView handler_test(HttpServletRequest request, HttpServletResponse response)throws Exception;
}
自己的适配器:
public class MyHandlerAdapter implements HandlerAdapter{
@Override
public boolean supports(Object handler) {
return (handler instanceof MyHandler);
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((MyHandler)handler).handler_test(request, response);
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1L;
}
}
自己的hander:(就是我们之前写的Controller)
public class TestController implements MyController{
@Override
public ModelAndView handler_test(HttpServletRequest request, HttpServletResponse response) throws Exception {
String name = request.getParameter("name");
ModelAndView mv = new ModelAndView("hello");
mv.addObject("name", name);
return mv;
}
}
5.处理器拦截器
SpringMVC的处理器拦截器类似于Servlet 开发中最后在spring的配置中把我们的适配器进行配置即可正常使用的过滤器Filter,用于对处理器进行预处理和后处理。
1)常见应用场景
1、日志记录
2、权限检查
3、性能监控
4、通用行为 例如读取用户cookie
5、OpenSessionInView 例如在Hibernate中,在进入处理器前打开Session,在完成后关闭Session。
等
2)拦截器接口
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception;
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception;
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
}
preHandle方法
预处理回调方法,实现处理器的预处理,第三个参数为的处理器(本次请求要访问的那个Controller)
返回值:true表示继续流程(如调用下一个拦截器或处理器)
false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应
postHandle方法
后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
afterCompletion方法
整个请求处理完毕回调方法,即在视图渲染完毕时回调
3)拦截器适配器
有时候我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor 接口的话,三个方法必须实现,不管你需不需要,此时spring 提供了一个HandlerInterceptorAdapter 适配器(适配器模式),允许我们只实现需要的回调方法。
在HandlerInterceptorAdapter中,对HandlerInterceptor 接口中的三个方法都进行了空实现,其中preHandle方法的返回值,默认是true
4)测试一个拦截器
拦截器代码:
public class MyInterceptor1 extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("MyInterceptor1 preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor1 postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("MyInterceptor1 afterCompletion");
}
}
配置文件:
<bean name="handlerInterceptor1" class="com.briup.web.interceptor.MyInterceptor1"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
</list>
</property>
</bean>
访问一个测试的Controller查看结果:
MyInterceptor1 preHandle
TestController执行
MyInterceptor1 postHandle
MyInterceptor1 afterCompletion
5)测试俩个拦截器
俩个拦截器的代码和上面类似,只是每个输出的内容不同
配置文件:
<bean name="handlerInterceptor1" class="com.briup.web.interceptor.MyInterceptor1"/>
<bean name="handlerInterceptor2" class="com.briup.web.interceptor.MyInterceptor1"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
<ref bean="handlerInterceptor2"/>
</list>
</property>
</bean>
访问一个测试的Controller查看结果:
MyInterceptor1 preHandle
MyInterceptor2 preHandle
TestController执行
MyInterceptor2 postHandle
MyInterceptor1 postHandle
MyInterceptor2 afterCompletion
MyInterceptor1 afterCompletion
注意:<list>标签中引用拦截器的顺序会影响结果输出的顺序
6)如果Controller等采用的注解配置,那么拦截器需要mvc标签进行配置
注意:每个<mvc:interceptor>只能配置一个拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<ref bean="handlerInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
例如1: 注意/*和/**的区别
<mvc:interceptors>
<!-- 下面所有的mvc映射路径都会被这个拦截器拦截 -->
<bean class="com.briup.web.interceptor.MyInterceptor1" />
<mvc:interceptor>
<!--过滤这些请求-->
<mvc:mapping path="/**"/>
<!--除了这些请求-->
<mvc:exclude-mapping path="/admin/**"/>
<bean class="com.briup.web.interceptor.MyInterceptor2" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/secure/*"/>
<bean class="com.briup.web.interceptor.MyInterceptor3" />
</mvc:interceptor>
7)拦截器是单例
因此不管多少用户请求多少次都只有一个拦截器实现,即线程不安全。
所以在必要时可以在拦截器中使用ThreadLocal,它是和线程绑定,一个线程一个ThreadLocal,A 线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal。
8)记录执行Controller所用时间
public class TimeInterceptor extends HandlerInterceptorAdapter{
//拦截器是单例,不是线程安全的,所以这里使用ThreadLocal
private ThreadLocal<Long> local = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long start = System.currentTimeMillis();
local.set(start);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
long end = System.currentTimeMillis();
System.out.println("共耗时:"+(end-local.get()));
}
}
配置:
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.briup.web.interceptor.TimeInterceptor" />
</mvc:interceptor>
9)登录检查
public class LoginInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//请求到登录页面放行
if(request.getServletPath().startsWith("/login")) {
return true;
}
//如果用户已经登录放行
if(request.getSession().getAttribute("username") != null) {
return true;
}
//重定向到登录页面
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
注意:推荐能使用servlet规范中的过滤器Filter实现的功能就用Filter实现,因为HandlerInteceptor只有在SpringWebMVC环境下才能使用,因此Filter是最通用的、最先应该使用的。
6.基于注解的SpringMVC
1)用于支持注解的配置
使用基于注解的配置可以省略很多操作,更方便。我们之前所看到的所有的xml配置,如果替换成基于注解只需要在spring的xml文件中做如下配置:
<mvc:annotation-driven/>
在Spring中,
处理器类可以使用 @Controller注解
业务逻辑层可以使用 @Service注解
数据持久层可以使用 @Repository注解
如果在处理器上使用 @Controller注解,那么还需要在配置文件中指定哪个包下面的类使用了该注解:
<context:component-scan base-package="com.briup.web.controller"></context:component-scan>
2)基于注解的Controller
使用注解后,就不需要再实现特定的接口,任意一个javaBean对象都可以当做处理器对象,对象中任意一个方法都可以作为处理器方法。
只需
在类上加上 @Controller注解
方法上加上 @RequestMapping注解
即可
例如:
web.xml中:
<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:spring-web-mvc.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>
src下面的spring-web-mvc.xml中:
<mvc:annotation-driven/>
<context:component-scan base-package="com.briup.web.controller"></context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
自定义的Controller中:
@Controller
public class HomeController {
@RequestMapping("/home")
public ModelAndView home(){
ModelAndView mv = new ModelAndView("index");
return mv;
}
}
如上代码,使用 @Controller表明HomeController类是一个处理器类,通过 @RequestMapping("/home")表明当url请求名为/home时,调用home方法执行处理,当处理完成之后返回ModelAndView对象。因为在spring-web-mvc.xml中配置了视图解析器的前缀和后缀,所以最后视图home.jsp被返回
3)基于注解的Controller的返回值
1.返回ModelAndView,和之前一样
2.返回String,表示跳转的逻辑视图名字,模型可以通过参数传过来
@Controller
public class HomeController {
@RequestMapping("/home")
public String home(Model model){
model.addAttribute("msg", "hello world");
return "index";
}
}
3.声明返回类型为void
可以通过参数获取request和response,分别使用服务器内部跳转和重定向,自己来决定要跳转的位置。
@Controller
public class HomeController {
@RequestMapping("/home")
public void home(HttpServletRequest request,HttpServletResponse response){
String username = request.getParameter("username");
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("hello world! "+username);
//或者使用servlet的方式进行跳转/重定向
}
}
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
1)拦截所有请求
此处需要特别强调的是 <url-pattern>/</url-pattern>使用的是/,而不是/*,如果使用/*,那么请求时可以通过DispatcherServlet转发到相应的Controller中,但是Controller返回的时候,如返回的jsp还会再次被拦截,这样导致404错误,即访问不到jsp。
‘
2)自定义拦截请求
拦截*.do、*.html、*.action, 例如/user/add.do
这是最传统的方式,最简单也最实用。不会导致静态文件(jpg,js,css)被拦截。
拦截/,例如:/user/add
可以实现REST风格的访问
弊端:会导致静态文件(jpg,js,css)被拦截后不能正常显示。
拦截/*,这是一个错误的方式,请求可以走到Controller中,但跳转到jsp时再次被拦截,不能访问到jsp。
3)静态资源的访问,如jpg,js,css
如果DispatcherServlet拦截"*.do"这样的有后缀的URL,就不存在访问不到静态资源的问题。
如果DispatcherServlet拦截"/",为了实现REST风格,拦截了所有的请求,那么同时对*.js,*.jpg等静态文件的访问也就被拦截了。
例如:
<link href="css/hello.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="js/hello.js"></script>
<img alt="none" src="images/logo.png">
解决方式一:利用Tomcat的defaultServlet来处理静态文件
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
特点:1.要配置多个,每种文件配置一个。
2.要写在DispatcherServlet的前面(和tomcat版本有关),让defaultServlet先拦截请求, 这样请求就不会进入Spring了
3. 高性能。
解决方式二: 使用<mvc:resources>标签,例如:
<mvc:resources mapping="/images/**" location="/images/"/>
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
mapping: 映射
两个*,表示映射指定路径下所有的URL,包括子路径
location:本地资源路径
这样如果有访问/images或者/js或者/css路径下面的资源的时候,spring就不会拦截了
解决方式三: 使用<mvc:default-servlet-handler/>标签
在spring配置文件中加入此标签配置即可
2.spring提供的编码过滤器
查看这个过滤器类源码便可知这里所传的俩个参数的作用
<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>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.Controller接口及其实现类
Controller是控制器接口,此处只有一个方法handleRequest,用于进行请求的功能处理,处理完请求后返回ModelAndView(Model模型数据部分 和 View视图部分)。
如果想直接在处理器/控制器里使用response向客户端写回数据,可以通过返回null来告诉DispatcherServlet我们已经写出响应了,不需要它进行视图解析
Spring默认提供了一些Controller接口的实现以方便我们使用,在Eclipse中选择Controller接口然后右键open type Hierarchy即可查看改接口的实现类,每个实现类都有自己特殊的功能,这里以实现类AbstractController为例简单介绍下。
查看AbstractController类中代码可知,我们写一个Controller的时候可以继承AbstractController然后实现handleRequestInternal方法即可。
提供了【可选】的会话的串行化访问功能,例如:
//即同一会话,线程同步
public class HelloWorldController extends AbstractController{
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
String name = request.getParameter("name");
//ModelAndView对象中包括了要返回的逻辑视图,以及数据模型
ModelAndView mv = new ModelAndView();
//设置视图名称,可以是字符串 也可以是视图对象
mv.setViewName("hello");
//设置数据模型
mv.addObject("name", name);
return mv;
}
}
<bean name="/hello" class="com.briup.web.controller.HelloWorldController">
<property name="synchronizeOnSession" value="true"></property>
</bean>
直接通过response写响应,例如:
public class HelloWorldController extends AbstractController{
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.getWriter().write("Hello World!!");
//如果想直接在该处理器/控制器写响应 可以通过返回null告诉DispatcherServlet自己已经写出响应了,不需要它进行视图解析
return null;
}
}
强制请求方法类型,例如:
//只支持post和get请求
<bean name="/hello" class="com.briup.web.controller.HelloWorldController">
<property name="supportedMethods" value="POST,GET"></property>
</bean>
当前请求的session前置条件检查,如果当前请求无session将抛出HttpSessionRequiredException异常,例如:
//在进入该控制器时,一定要有session存在,否则抛出HttpSessionRequiredException异常。
<bean name="/hello" class="com.briup.web.controller.HelloWorldController">
<property name="requireSession" value="true"/>
</bean>
4.自定义适配器
一般情况下,springMVCSimpleControllerHandlerAdapter会是我们常用的适配器,也是SpringMVC中默认的适配器,该适配器中的主要代码如下:
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
}
从代码中可以看出,它首先会判断我们的handler是否实现了Controller接口,如果实现了,那么会调用Controller接口中的handleRequest方法
那么根据这种方式能看出,我们也可以有自己的适配器的实现,
那么就可以让任意类成为SpringMVC中的handler了,
无论我们的类是否实现了Controller接口
例如:
自己的接口:
public interface MyController {
public ModelAndView handler_test(HttpServletRequest request, HttpServletResponse response)throws Exception;
}
自己的适配器:
public class MyHandlerAdapter implements HandlerAdapter{
@Override
public boolean supports(Object handler) {
return (handler instanceof MyHandler);
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((MyHandler)handler).handler_test(request, response);
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1L;
}
}
自己的hander:(就是我们之前写的Controller)
public class TestController implements MyController{
@Override
public ModelAndView handler_test(HttpServletRequest request, HttpServletResponse response) throws Exception {
String name = request.getParameter("name");
ModelAndView mv = new ModelAndView("hello");
mv.addObject("name", name);
return mv;
}
}
5.处理器拦截器
SpringMVC的处理器拦截器类似于Servlet 开发中最后在spring的配置中把我们的适配器进行配置即可正常使用的过滤器Filter,用于对处理器进行预处理和后处理。
1)常见应用场景
1、日志记录
2、权限检查
3、性能监控
4、通用行为 例如读取用户cookie
5、OpenSessionInView 例如在Hibernate中,在进入处理器前打开Session,在完成后关闭Session。
等
2)拦截器接口
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception;
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception;
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
}
preHandle方法
预处理回调方法,实现处理器的预处理,第三个参数为的处理器(本次请求要访问的那个Controller)
返回值:true表示继续流程(如调用下一个拦截器或处理器)
false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应
postHandle方法
后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
afterCompletion方法
整个请求处理完毕回调方法,即在视图渲染完毕时回调
3)拦截器适配器
有时候我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor 接口的话,三个方法必须实现,不管你需不需要,此时spring 提供了一个HandlerInterceptorAdapter 适配器(适配器模式),允许我们只实现需要的回调方法。
在HandlerInterceptorAdapter中,对HandlerInterceptor 接口中的三个方法都进行了空实现,其中preHandle方法的返回值,默认是true
4)测试一个拦截器
拦截器代码:
public class MyInterceptor1 extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("MyInterceptor1 preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor1 postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("MyInterceptor1 afterCompletion");
}
}
配置文件:
<bean name="handlerInterceptor1" class="com.briup.web.interceptor.MyInterceptor1"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
</list>
</property>
</bean>
访问一个测试的Controller查看结果:
MyInterceptor1 preHandle
TestController执行
MyInterceptor1 postHandle
MyInterceptor1 afterCompletion
5)测试俩个拦截器
俩个拦截器的代码和上面类似,只是每个输出的内容不同
配置文件:
<bean name="handlerInterceptor1" class="com.briup.web.interceptor.MyInterceptor1"/>
<bean name="handlerInterceptor2" class="com.briup.web.interceptor.MyInterceptor1"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
<ref bean="handlerInterceptor2"/>
</list>
</property>
</bean>
访问一个测试的Controller查看结果:
MyInterceptor1 preHandle
MyInterceptor2 preHandle
TestController执行
MyInterceptor2 postHandle
MyInterceptor1 postHandle
MyInterceptor2 afterCompletion
MyInterceptor1 afterCompletion
注意:<list>标签中引用拦截器的顺序会影响结果输出的顺序
6)如果Controller等采用的注解配置,那么拦截器需要mvc标签进行配置
注意:每个<mvc:interceptor>只能配置一个拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<ref bean="handlerInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
例如1: 注意/*和/**的区别
<mvc:interceptors>
<!-- 下面所有的mvc映射路径都会被这个拦截器拦截 -->
<bean class="com.briup.web.interceptor.MyInterceptor1" />
<mvc:interceptor>
<!--过滤这些请求-->
<mvc:mapping path="/**"/>
<!--除了这些请求-->
<mvc:exclude-mapping path="/admin/**"/>
<bean class="com.briup.web.interceptor.MyInterceptor2" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/secure/*"/>
<bean class="com.briup.web.interceptor.MyInterceptor3" />
</mvc:interceptor>
7)拦截器是单例
因此不管多少用户请求多少次都只有一个拦截器实现,即线程不安全。
所以在必要时可以在拦截器中使用ThreadLocal,它是和线程绑定,一个线程一个ThreadLocal,A 线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal。
8)记录执行Controller所用时间
public class TimeInterceptor extends HandlerInterceptorAdapter{
//拦截器是单例,不是线程安全的,所以这里使用ThreadLocal
private ThreadLocal<Long> local = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long start = System.currentTimeMillis();
local.set(start);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
long end = System.currentTimeMillis();
System.out.println("共耗时:"+(end-local.get()));
}
}
配置:
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.briup.web.interceptor.TimeInterceptor" />
</mvc:interceptor>
9)登录检查
public class LoginInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//请求到登录页面放行
if(request.getServletPath().startsWith("/login")) {
return true;
}
//如果用户已经登录放行
if(request.getSession().getAttribute("username") != null) {
return true;
}
//重定向到登录页面
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
注意:推荐能使用servlet规范中的过滤器Filter实现的功能就用Filter实现,因为HandlerInteceptor只有在SpringWebMVC环境下才能使用,因此Filter是最通用的、最先应该使用的。
6.基于注解的SpringMVC
1)用于支持注解的配置
使用基于注解的配置可以省略很多操作,更方便。我们之前所看到的所有的xml配置,如果替换成基于注解只需要在spring的xml文件中做如下配置:
<mvc:annotation-driven/>
在Spring中,
处理器类可以使用 @Controller注解
业务逻辑层可以使用 @Service注解
数据持久层可以使用 @Repository注解
如果在处理器上使用 @Controller注解,那么还需要在配置文件中指定哪个包下面的类使用了该注解:
<context:component-scan base-package="com.briup.web.controller"></context:component-scan>
2)基于注解的Controller
使用注解后,就不需要再实现特定的接口,任意一个javaBean对象都可以当做处理器对象,对象中任意一个方法都可以作为处理器方法。
只需
在类上加上 @Controller注解
方法上加上 @RequestMapping注解
即可
例如:
web.xml中:
<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:spring-web-mvc.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>
src下面的spring-web-mvc.xml中:
<mvc:annotation-driven/>
<context:component-scan base-package="com.briup.web.controller"></context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
自定义的Controller中:
@Controller
public class HomeController {
@RequestMapping("/home")
public ModelAndView home(){
ModelAndView mv = new ModelAndView("index");
return mv;
}
}
如上代码,使用 @Controller表明HomeController类是一个处理器类,通过 @RequestMapping("/home")表明当url请求名为/home时,调用home方法执行处理,当处理完成之后返回ModelAndView对象。因为在spring-web-mvc.xml中配置了视图解析器的前缀和后缀,所以最后视图home.jsp被返回
3)基于注解的Controller的返回值
1.返回ModelAndView,和之前一样
2.返回String,表示跳转的逻辑视图名字,模型可以通过参数传过来
@Controller
public class HomeController {
@RequestMapping("/home")
public String home(Model model){
model.addAttribute("msg", "hello world");
return "index";
}
}
3.声明返回类型为void
可以通过参数获取request和response,分别使用服务器内部跳转和重定向,自己来决定要跳转的位置。
@Controller
public class HomeController {
@RequestMapping("/home")
public void home(HttpServletRequest request,HttpServletResponse response){
String username = request.getParameter("username");
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("hello world! "+username);
//或者使用servlet的方式进行跳转/重定向
}
}