欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SpringMVC(AbstractController,拦截器,注解)

程序员文章站 2022-06-20 17:08:48
1.Controller接口及其实现类 Controller是控制器/处理器接口,只有一个方法handleRequest,用于进行请求的功能处理(功能处理方法),处理完请求后返回ModelAndView对象(Model模型数据部分 和 View视图部分)。 如果想直接在处理器/控制器里使用respo ......

1.controller接口及其实现类
controller是控制器/处理器接口,只有一个方法handlerequest,用于进行请求的功能处理(功能处理方法),处理完请求后返回modelandview对象(model模型数据部分 和 view视图部分)。

如果想直接在处理器/控制器里使用response向客户端写回数据,可以通过返回null来告诉dispatcherservlet我们已经写出响应了,不需要它进行视图解析

spring默认提供了一些controller接口的实现类以方便我们使用,在eclipse中选择controller接口然后右键open type hierarchy即可查看该接口的实现类,每个实现类都有自己特殊的功能,这里以实现类abstractcontroller为例简单介绍下。
查看abstractcontroller类中代码可知,我们写一个controller的时候可以继承abstractcontroller然后实现handlerequestinternal方法即可。

提供了【可选】的会话(session)的串行化访问功能,例如:
即同一会话,线程同步

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>

 

2.springmvc中的拦截器

springmvc的处理器拦截器类似于servlet 开发中的过滤器filter,用于对处理器进行预处理和后处理。拦截器的作用有局限性只可以作用于处理器

1)常见应用场景
1、日志记录
2、权限检查
3、性能监控
4、通用行为 例如读取用户cookie等

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="包名.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.myinterceptor2"/>

<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)拦截器mvc标签进行配置
注意:每个<mvc:interceptor>只能配置一个拦截器

<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <ref bean="handlerinterceptor1"/>
  </mvc:interceptor>
</mvc:interceptors>

 

例如1: 注意/*和/**的区别

<bean name="myinter1" class="com.briup.web.interceptor.myinterceptor1" />

<bean class="com.briup.web.interceptor.myinterceptor2" />

<bean class="com.briup.web.interceptor.myinterceptor3" />

<!-- mvc提供的拦截器配置方式 -->
<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <ref bean="myinter1"/>
  </mvc:interceptor>

  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <mvc:exclude-mapping path="/test"/>
    <ref bean="myinter2"/>
  </mvc:interceptor>

  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <ref bean="timeinter"/>
  </mvc:interceptor>
</mvc:interceptors>

7)拦截器是单例
因此不管多少用户请求多少次都只有一个拦截器实现,即线程不安全。
所以在必要时可以在拦截器中使用threadlocal,它是和线程绑定,一个线程一个threadlocal,a 线程的threadlocal只能看到a线程的threadlocal,不能看到b线程的threadlocal。
举个例子:
记录执行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()));
  }
}

8)登录检查

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是最通用的、最先应该使用的。

3.基于注解的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>

    <!-- 使容器可以识别mvc的注解 -->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!-- 扫描指定包及其子包下所有的注解 -->
    <context:component-scan base-package="com.briup.annotation"/>

 

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的方式进行跳转/重定向
  }
}

可以写一个类来测试所有方法:

@controller
public class hellocontroller {
    
    //@requestmapping("/test1")
    //@requestmapping(value = "/test1")
    @requestmapping(value= {"/test1","/annotest1"})
    public modelandview test1(httpservletrequest req,
            httpservletresponse reqs) throws exception{
        
        modelandview mv = new modelandview("test");
        mv.addobject("name", "李四");
        return mv;
    }
    
    //如果方法返回值的类型为string,则表示返回的逻辑视图名
    @requestmapping("/test2")
    public string test2() throws exception{
        return "hello";
    }
    
    @requestmapping("/test3")
    public string test3(model model) throws exception{
        model.addattribute("name", "王五");
        return "hello";
    }
    
    @requestmapping("/test4")
    public void test4(httpservletresponse response) throws exception{
        response.getwriter().write("hello world!");
        response.getwriter().flush();
    }
    
    /*
     * 如果方法没有返回值,并且不通过响应用流的方式给客户端写回数据。
     * 那么,springmvc会自动将@requestmapping里面的参数,作为逻辑视图名
     */
    @requestmapping("/test5")
    public void test5() throws exception{
        system.out.println("--------------");
    }
    
    @requestmapping("/test6")
    public void test6(httpservletrequest request,
            httpservletresponse response) throws exception{
        string path = "/web-inf/jsp/hello.jsp";
        request.setattribute("name", "赵四");
        request.getrequestdispatcher(path).forward(request, response);
    }
    
    @requestmapping("/test7")
    public void test7(httpservletrequest request,
            httpservletresponse response) throws exception{
        string path = "/web-inf/jsp/hello.jsp";
        response.sendredirect(request.getcontextpath()+"/test6");
    }
}

 

4)spring2.5中引入注解对控制器/处理器(controller/handler)支持
@controller
用于标识是控制器/处理器类;
@requestmapping
请求到处理器功能方法的映射规则;
@requestparam
请求参数到处理器功能处理方法的方法参数上的绑定;
@modelattribute
请求参数到命令对象的绑定;
@sessionattributes
用于声明session 级别存储的属性,放置在处理器类上,通常列出模型属性(如@modelattribute)对应的名称,则这些属性会透明的保存到session 中
@initbinder
自定义数据绑定注册支持,用于将请求参数转换到命令对象属性的对应类型;


5).spring3引入了更多的注解,其中包含了对restful架构风格的支持
@cookievalue
cookie数据到处理器功能处理方法的方法参数上的绑定;
@requestheader
请求头数据到处理器功能处理方法的方法参数上的绑定;
@requestbody
请求的body体的绑定
@responsebody
处理器功能处理方法的返回值作为响应体
@responsestatus
定义处理器功能处理方法/异常处理器返回的状态码和原因;
@exceptionhandler
注解式声明异常处理器;
@pathvariable
请求uri 中的模板变量部分到处理器功能处理方法的方法参数上的绑定,从而支持restful架构风格的uri;

6).spring3中引入的mvc命名空间
mvc这个命名空间是在spring3中引入的,其作用是用来支持mvc的配置
需要在<beans>中声明出这个命名空间及其对应的schemalocation中的值
<mvc:annotation-driven>
自动注册基于注解风格的映射器和适配器:(也就是说这个mvc标签是基于注解的)
在spring2.5中是defaultannotationhandlermapping和annotationmethodhandleradapter

在spring3中是requestmappinghandlermapping和requestmappinghandleradapter.
同时还支持各种数据的转换器.

配置自定义的处理器拦截器,例如:

<mvc:interceptors>
    <mvc:interceptor>
      <mvc:mapping path="/**"/>
        <ref bean="handlerinterceptor1"/>
    </mvc:interceptor>
</mvc:interceptors>

收到相应请求后直接选择相应的视图,例如:
<mvc:view-controller path="/hello" view-name="test"></mvc:view-controller>

逻辑静态资源路径到物理静态资源路径的对应.例如:解决了静态资源拦截的问题

<mvc:resources mapping="/images/**" location="/images/"/> 
<mvc:resources mapping="/js/**" location="/js/"/> 
<mvc:resources mapping="/css/**" location="/css/"/> 

<mvc:default-servlet-handler>

当在web.xml中dispatcherservlet使用<url-pattern>/</url-pattern> 映射的时候,会静态资源也映射了,如果配置了这个mvc标签,那么再访问静态资源的时候就转交给默认的servlet来响应静态文件,否则报404 找不到静态资源错误。

7).@controller和@requestmapping注解

1声明处理器

@controller
public class helloworldcontroller {

}

 

2映射处理器中的【功能处理方法】

@controller
public class helloworldcontroller {
  @requestmapping("/home")
  public modelandview home(){
    modelandview mv = new modelandview("index");
    return mv;
  }
}

表明该方法映射的url路径为/home

3@requestmapping也可以写在处理器类上

// /test/home
@requestmapping("/test")
@controller
public class homecontroller {
  @requestmapping("/home")
  public modelandview home(){
    modelandview mv = new modelandview("index");
    return mv;
  }
}

表明该方法映射的url路径为/test/home

8)请求映射@requestmapping
假设浏览器发送了一个请求如下:
-------------------------------
post /login http1.1
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-encoding: gzip, deflate
accept-language: zh-cn,en;q=0.8,zh;q=0.5,en-us;q=0.3
connection: keep-alive
cookie: jsessionid=dbc6367deb1c024a836f3ea35fcfd5a2
host: 127.0.0.1:8989
upgrade-insecure-requests: 1
user-agent: mozilla/5.0 (windows nt 6.1; wow64; rv:49.0) gecko/20100101 firefox/49.0

username=tom&password=123
--------------------------------


http协议的请求格式如下:
---------------------------------
请求方法 url 协议版本号
请求头信息
请求头信息
请求头信息
..
回车换行
请求正文
---------------------------------


从格式中我们可以看到【请求方法、url、请求头信息、请求正文】这四部分一般是可变的,因此我们可以把请求中的这些信息在处理器的【功能处理方法】中进行的映射,因此请求的映射分为如下几种:
url路径映射
使用url映射到处理器的功能处理方法;
请求方法映射限定
例如限定功能处理方法只处理get请求;
请求参数映射限定
例如限定只处理包含username参数的请求;
请求头映射限定
例如限定只处理"accept=application/json"的请求。