spring mvc
程序员文章站
2022-07-13 13:58:22
...
spring-mvc
如图
请求首先通过DispatcherServlet。servlet根据HandlerMapping,来处理请求,并根据请求,来找到Controller,Controller执行完毕后,发送一个ModelAndView,并告诉需要展示哪个视图。根据这个视图,servlet找到这个视图的ViewResolver,并由这个ViewResolver生成对应的view,并输出。
配置servlet
springmvc是基于servlet的,因此需要在web.xml配置。
<servlet> <servlet-name>roadrantz</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet>
默认情况下,DispatcherServlet会加载这个servletname-servlet.xml文件,将这个文件作为spring的配置文件(淡然可以和全局的加载器,也就是全局的监听器和监听器加载的配置文件结合使用)。如上面我们定义的servlet-name的名字是roadrantz,因此它会加载roadrantz-servlet.xml。
之后当然是要配置这个servlet对应的映射的了。
<servlet-mapping> <servlet-name>roadrantz</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping>
事实上,我们应该把配置分成多个文件。这样,基于springmvc的配置只在servletname-servlet.xml中,和其他部分的配置(如事务管理,数据源等配置则在另外一个地方,因为他们是通用的)是分开的。
WebApplicationContext
WebApplicationContext是ApplicationContext的子类它提供了为WEB应用服务的更多功能。
我们可以通过RequestContextUtils来获取WebApplicationContext
DispatcherServlet
会配置如下的bean
Bean type Explanation
controllers mvc中的C
handler mappings 处理器影射器,它会根据请求,查找到实际的请求处理者
view resolvers 视图解析器
locale resolver 本地化解析器,提供国际化的支持
Theme resolver 主题解析器
multipart file 文件上传解析器
handler exception resolvers 异常处理器
DispatcherServlet配置完成后,当相应的请求到达时,处理就开始了。 处理流程是
1.找到WebApplicationContext并将其绑定到请求的一个属性上, 以便控制器和处理链上的其它处理器能使用WebApplicationContext。 默认的属性名为DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE。
2.将本地化解析器(localResolver)绑定到请求上,这样使得处理链上的处理器在处理请求(准备数据、显示视图等等) 时能进行本地化处理。若不使用本地化解析器,也不会有任何副作用,因此如果不需要本地化解析,忽略它即可。
3.将主题解析器绑定到请求上,这样视图可以决定使用哪个主题。如果你不需要主题,可以忽略它,不会有任何影响。
4.如果上传文件解析器被指定,Spring会检查每个接收到的请求是否存在上传文件,如果存在, 这个请求将被封装成MultipartHttpServletRequest以便被处理链中的其它处理器使用 (关于文件上传的更多内容请参考Section 13.8.2, “使用MultipartResolver”)。
5.找到合适的处理器,执行和这个处理器相关的执行链(预处理器,后置处理器,控制器),以便为视图准备模型数据(用于渲染)。
6.如果模型数据被返回,就使用配置在WebApplicationContext中的视图解析器显示视图, 否则视图不会被显示。有多种原因可以导致返回的数据模型为空,比如预处理器或后处理器可能截取了请求,这可能是出于安全原因, 也可能是请求已经被处理,没有必要再处理一次。
DispatcherServlet的初始化参数
contextClass 实现了WebApplicationContext的类。默认是XmlWebApplicationContext。
contextConfigLoSctraintgitohnat 与全局的contextConfigLoSctraintgitohnat参数可以共存
namespace WebApplicationContext的命名空间。默认是[servlet-name]-servlet
多个配置文件的方式
1.基于监听器的方式:
定义监听器
<listener> <listener-class>org.springframework. web.context.ContextLoaderListener</listener-class> </listener>以及配置全局监听器的配置属性。
注意
有些比较老的容器,在初始化servlet之前,并不会初始化监听器,因此如果可能会被部署到这样的容器的话,需要将监听器,改成另外一个servlet
ContextLoaderServlet
将它置于DispatcherServlet之前。
不管是监听器还是ContextLoaderServlet,这两个全局的装载器,在没有指定配置文件的情况下,会查找/WEB-INF/applicationContext.xml。
但我们会有更多的配置文件,可以通过属性contextConfigLocation来设置
如
<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spitter-security.xml classpath:service-context.xml classpath:persistence-context.xml classpath:dataSource-context.xml </param-value> </context-param>这个属性的值,和spring的资源Resource加载方式一样,可以带classpath:,file:,等前缀。
这里先给出一个非注解方式的使用方式。
主页
主页是一个web应用必须有的(这里说的必须,你懂得)。
当然先需要一个controller了。
package com.roadrantz.mvc; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; import com.roadrantz.service.RantService; public class HomePageController extends AbstractController { public HomePageController() { } protected ModelAndView handleRequestInternal( HttpServletRequest request, HttpServletResponse response) throws Exception { List recentRants = rantService.getRecentRants(); //定义modelandview,home表示会返回home.jsp(是不是jsp由视图解析器决定)。 return new ModelAndView("home", "rants", recentRants); } private RantService rantService; public void setRantService(RantService rantService) { this.rantService = rantService; } }
ModelAndView对象
ModelAndView封装了视图已经模型数据。
注意,如果一个controller,return类型非null或者非viod,且没有写@ResponseBody注解的,最后都会被封装成ModelAndView,对与返回的一个普通的bean的时候,分装后的ModelAndView如下图
new ModelAndView("home", "rants", recentRants);
如上,第一个参数是视图名字。之后的参数是以模型对象将被传递给视图。
下面是配置这个controller
<bean name="/home.htm" class="com.roadrantz.mvc.HomePageController"> <property name="rantService" ref="rantService" /> </bean>这里首先没有使用id,而是使用name,这里的原因是因为有特殊字符/和.id不支持。而使用name。
当一个请求home.htm(这个请求位于根目录下,如果非根目录是无法访问的,如127.0.0.1/daowole/home.htm是可以访问的,但是127.0.0.1/daowole/abc/home.htm是无法访问的。如果想要它不管通过哪个目录,只要是最后的资源是home.htm都可以访问,可以把bean的name改成name="/**/home.htm",那么127.0.0.1/daowole/abc/home.htm还是127.0.0.1/daowole/home.htm都可以访问了。)的话,那么就会被访问到这里来。这里可以发现我们无需配置HandlerMapping,因为springmvc有一个默认的handlermapping,BeanNameUrlHandlerMapping。它是使用URL模式的基本名字。
由于上面的的视图使用的是jsp视图,因此直接return一个jsp页面,自然没有问题了。
而对应jsp视图,springmvc自然还提供了其他配置(相对普通jsp而言),这需要使用到一个jsp的解析器,org.springframework.web.servlet.view.InternalResourceViewResolver。
<bean id="viewResolver" class="org.springframework.web. servlet.view.InternalResourceViewResolver"> <property name="prefix"> <value>/WEB-INF/jsp/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean>这个会再controller返回的时候,会拼装前缀和后缀,再查找文件。如果找到,它来处理是自然的。
home拼装前缀和后缀后就是/WEB-INF/jsp/home.jsp
对于spring3.0版本的,如果使用了Spring 3.0.4或以上版本的话,可以使用
<mvc:resources location="" mapping=""/>
在servletname-servlet.xml文件中。
这种情况就解决了如果我们把url映射定义为/(这会由springmvc处理所有的请求),那么图片,js等静态资源会被由springmvc处理。它会把mapping指定的路径(ant风格)映射到location中。
<mvc:resources mapping="/resources/**" location="/resources/" />将会把所有以resources开头的资源,映射到/resources/文件夹去。
HandlerMapping
HandlerMapping负责查找对应的controller。spring提供了几个默认的HandlerMapping。
BeanNameUrlHandlerMapping,这个是默认的。会根据请求过来的url,作为bean name,来查找这个bean name。
ControllerBeanNameHandlerMapping ControllerClassNameHandlerMapping 比较相似,前者根据applicationContext中controller的bean名称做映射,后者直接根据controller的类名做映射。
SimpleUrlHandlerMapping 它在应用上下文(spring applicationContext)中可以进行配置,并且有Ant风格的路径匹配功能。
ControllerClassNameHandlerMapping 它使用惯例来确定请求的URL和用于处理它们的Controller实例间的映射关系。 考虑下面的(直观的)Controller实现, 请特别注意这个类的名称。
public class ViewShoppingCartController implements Controller {
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean id="viewShoppingCart" class="x.y.z.ViewShoppingCartController"> <!-- inject dependencies as required... --> </bean>ControllerClassNameHandlerMapping在它的应用上下文中找出所有不同的处理器(handler)(或Controller)bean, 并去掉名称中的Controller,来定义它的处理器映射。
让我们看更多的例子,这样其中的中心思想就马上就清楚了。
WelcomeController映射到“/welcome*”请求URL
HomeController映射到“/home*”请求URL
IndexController映射到“/index*”请求URL
RegisterController映射到“/register*”请求URL
DisplayShoppingCartController映射到“/displayshoppingcart*请求URL
(注意大小写——全部小写——对于驼峰式大小写(第一个词的首字母小写,随后的每个词首字母大写)的Controller类名。)
当控制器是MultiActionController处理器类时,生成的映射就(有一点点)更为复杂
AdminController映射到“/admin/*”请求URL
CatalogController映射到“/catalog/*”请求URL
CommonsPathMapHandlerMapping CommonsPathMapHandlerMapping 应用了 jdk1.5 后的新特性 通过 Controller 中的注释 进行映射
在类的主是中加入 @@org.springframework.web.servlet.handler.commonsattributes.PathMap("/path.do")(这个写在java doc注释中)
如
/** * @@org.springframework.web.servlet.handler. commonsattributes.PathMap("/hello.do") */ public class HelloController extends AbstractCommandController { ... }
<bean id="urlMapping" class="org.springframework.web.servlet.handler.metadata.CommonsPathMapHandlerMapping" />
SimpleUrlHandlerMapping
<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/**/test.html">homeController</prop> </props> </property> </bean>如上,当任何一种方式访问到test.html,都由id=homeController的bean处理
mappings是一个java.util.Properties类型的。
ControllerClassNameHandlerMapping使用
package com.cgodo.daowole.action; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; public class SampleController extends AbstractController { protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { ModelAndView mav = new ModelAndView("index"); mav.addObject("message", "Hello World!"); return mav; } }
<bean id="urlMapping" class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" />如上,我们可以通过test.html(这里的后缀.html和你的web.xml配置有关)。就能访问。而不需要再增加其他的配置了。
DefaultAnnotationHandlerMapping 根据@RequestMapping注解要查找action
对于支持DefaultAnnotationHandlerMapping 版本的spring,如果没有对应
handlermapping,那么DispatcherServlet会分别建立
BeanNameUrlHandlerMapping
和
DefaultAnnotationHandlerMapping
多个handlemapping同时处理,也是可以的。我们也可以通过order属性。来配置mapping的排序。
如
<bean id="beanNameUrlMapping" class="org.springframework.web.
➥ servlet.handler.BeanNameUrlHandlerMapping">
<property name="order"><value>1</value></property>
</bean>
<bean id="simpleUrlMapping" class="org.springframework.web.
➥ servlet.handler.SimpleUrlHandlerMapping">
<property name="order"><value>0</value></property>
<property name="mappings">
…
</property>
</bean>
这里的话,SimpleUrlHandlerMapping排序是0,因此它首先被servlet询问,如果这个SimpleUrlHandlerMapping有结果回来(标示它来处理),那么就进行处理,而没有的话,将询问下一个,也就是BeanNameUrlHandlerMapping。
控制器
如图,spring的控制器,由Controller接口定义。
可以将控制器归类为6类
View类型:
ParameterizableViewController
UrlFilenameViewController
当控制器只需要显示静态视图时。
Simple类型
Controller (interface)
AbstractController
Throwaway类型
ThrowawayController
Multiaction类型
MultiActionController
当action中有多个执行代码(方法)。
Command类型
BaseCommandController
AbstractCommandController
action可以获取请求的一个或多个参数,并将参数封装成一个对象。还能对参数进行验证。
Form类型
AbstractFormController
SimpleFormController
拥有表单处理功能。
Wizard类型
AbstractWizardFormController
当一个应用,由多个步骤组成,每一个步骤走完之后,得到一个结果,类似向导。
AbstractCommandController
直接继承AbstractController,自然可以访问到request,来获取参数,完成参数验证,但是这样会让你的action变得复杂。
package com.cgodo.daowole.action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractCommandController;
import com.cgodo.daowole.model.Page;
@SuppressWarnings("deprecation")
public class SampleController extends AbstractCommandController {
public SampleController() {
setCommandClass(Page.class);
setCommandName("page");
}
@SuppressWarnings("unchecked")
protected ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
ModelAndView mav = new ModelAndView("index", "message", "hello!");
@SuppressWarnings("unused")
Page<String> page = (Page<String>) command;
return mav;
}
}
如上所示。
访问
http://127.0.0.1:8080/daowole/test.html?pageNo=2
可以发现,pageNo被注入到了command变量中。
验证
编写验证类
package com.cgodo.daowole.action.validator;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import com.cgodo.daowole.model.Page;
public class PageValidator implements Validator {
public boolean supports(Class<?> arg0) {
return arg0.equals(Page.class);
}
public void validate(Object command, Errors arg1) {
Page<String> page = (Page<String>) command;
if (page.getPageNo() < 2) {
arg1.rejectValue("pageNO", "request.pageNo", "请输入pageNo");
}
}
}
修改bean的定义
<bean id="homeController" name="/**/home.html"
class="com.cgodo.daowole.action.SampleController">
<property name="formView" value="input" />
<property name="successView" value="index" />
<property name="validator">
<bean class="com.cgodo.daowole.action.validator.PageValidator" />
</property>
</bean>
SimpleFormController
package com.roadrantz.mvc;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import com.roadrantz.domain.Rant;
import com.roadrantz.domain.Vehicle;
import com.roadrantz.service.RantService;
public class AddRantFormController extends SimpleFormController {
private static final String[] ALL_STATES = {
"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FL",
"GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME",
"MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH",
"NJ", "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI",
"SC", "SD", "TN", "TX", "UT", "VA", "VT", "WA", "WV", "WI",
"WY"
};
public AddRantFormController() {
setCommandClass(Rant.class);
setCommandName("rant");
}
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
Rant rantForm = (Rant) super.formBackingObject(request);
rantForm.setVehicle(new Vehicle());
return rantForm;
}
protected Map referenceData(HttpServletRequest request)
throws Exception {
Map referenceData = new HashMap();
referenceData.put("states", ALL_STATES);
return referenceData;
}
protected ModelAndView onSubmit(Object command,
BindException bindException) throws Exception {
Rant rant = (Rant) command;
rantService.addRant(rant);
return new ModelAndView(getSuccessView());
}
private RantService rantService;
public void setRantService(RantService rantService) {
this.rantService = rantService;
}
}
<bean id="addRantController"
class="com.roadrantz.mvc.AddRantFormController">
<property name="formView" value="addRant" />
<property name="successView" value="rantAdded" />
<property name="rantService" ref="rantService" />
</bean>
验证
编写验证类
package com.roadrantz.mvc;
import org.apache.oro.text.perl.Perl5Util;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.roadrantz.domain.Rant;
public class RantValidator implements Validator {
public boolean supports(Class clazz) {
return clazz.equals(Rant.class);
}
public void validate(Object command, Errors errors) {
Rant rant = (Rant) command;
ValidationUtils.rejectIfEmpty(
errors, "vehicle.state", "required.state",
"State is required.");
ValidationUtils.rejectIfEmpty(
errors, "vehicle.plateNumber", "required.plateNumber",
"The license plate number is required.");
ValidationUtils.rejectIfEmptyOrWhitespace(
errors, "rantText", "required.rantText",
"You must enter some rant text.");
validatePlateNumber(
rant.getVehicle().getPlateNumber(), errors);
}
private static final String PLATE_REGEXP =
"/[a-z0-9]{2,6}/i";
private void validatePlateNumber(
String plateNumber, Errors errors) {
Perl5Util perl5Util = new Perl5Util();
if(!perl5Util.match(PLATE_REGEXP, plateNumber)) {
errors.reject("invalid.plateNumber",
"Invalid license plate number.");
}
}
}
修改bean的定义。
<bean id="addRantController"
class="com.roadrantz.mvc.AddRantFormController">
<property name="formView" value="addRant" />
<property name="successView" value="rantAdded" />
<property name="rantService" ref="rantService" />
<property name="validator">
<bean class="com.roadrantz.mvc.RantValidator" />
</property>
</bean>
例子
package com.cgodo.daowole.service.imp;
import org.springframework.stereotype.Service;
import com.cgodo.daowole.service.ITest;
@Service
public class MyTest implements ITest {
public void test() {
}
}
package com.cgodo.daowole.action;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.cgodo.daowole.service.ITest;
@Controller
public class ActionTest {
private ITest test;
public ITest getTest() {
return test;
}
@Autowired
public void setTest(ITest test) {
this.test = test;
}
@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "index";
}
@RequestMapping("/input")
public String input(String input) {
System.out.println("name is " + input);
return "index";
}
}
com.cgodo.daowole.spring.applicationContext包下面
applicationContext.xml
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!-- 开启注解支持 ,将会对注解进行处理-->
<context:annotation-config />
<!--
开启自动代理,自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确认通知是否如期进行。
如果想强制使用CGLIB代理,需要将 <aop:aspectj-autoproxy> 的 proxy-target-class
属性设为true。
-->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- 扫描的包,spring将自动扫描这些包,根据其的注解配置,自动进行配置 -->
<context:component-scan
base-package="com.cgodo.daowole.dao, com.cgodo.daowole.service, com.cgodo.daowole.aop" />
<!-- 注解驱动开启,对注解方式的事物进行支持 -->
<tx:annotation-driven mode="aspectj" />
<!-- 数据源配置 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://10.13.166.224:3306/daowole?useUnicode=false&autoReconnect=true&characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>
<!-- 事物管理员 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
WEB-INF文件夹下面
springMVC-servlet.xml
<?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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"
default-autowire="byName">
<context:component-scan base-package="com.cgodo.daowole.action" />
<mvc:annotation-driven />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>daowole</display-name>
<!-- <context-param>-->
<!-- <param-name>contextConfigLocation</param-name>-->
<!-- <param-value></param-value>-->
<!-- </context-param>-->
<!---->
<!-- <listener>-->
<!--
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
-->
<!-- </listener>-->
<servlet>
<description>
test</description>
<display-name>test</display-name>
<servlet-name>test</servlet-name>
<servlet-class>com.cgodo.daowole.test.ServletTest</servlet-class>
</servlet>
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springMVC-servlet.xml, classpath:com/cgodo/daowole/spring/applicationContext/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<!-- <welcome-file>index.jsp</welcome-file>-->
<!-- <welcome-file>default.html</welcome-file>-->
<!-- <welcome-file>default.htm</welcome-file>-->
<!-- <welcome-file>default.jsp</welcome-file>-->
</welcome-file-list>
</web-app>
上面的配置可以看出来,
第一点,连接器初始化的配置文件和spring mvc 的servlet初始化的配置,可以互相访问。
第二点,<context:component-scan base-package 方式定义的扫描范围出现在多个配置文件是不会覆盖的。
基于注解的springmvc
基于注解的话,需要添加几个配置内容到servletname-servlet.xml
基于注解的方式,可能不仅仅依赖与DefaultAnnotationHandlerMapping,但我们只需要添加一句
<mvc:annotation-driven/>
它提供了springmvc注解的所有支持,以及JSR-303的注解支持。
package com.cgodo.daowole.action;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class AnnotationAction {
@RequestMapping( { "/", "/home.do" })
public String home() {
return "index";
}
}
这里home方法可以接受两个请求(多个方法使用{}包括起来,里面是多个字符串组成,使用逗号分割),根请求和home.do请求。
这里的路径也支持ant路径风格。
springmvc的路径匹配规则和servlet的匹配规则一样,由匹配的严格程度从高到低,最先匹配的处理。更底级别的严格程度匹配规则就不处理。
由于这里类也是基于注解的,(这里如果不用@Controller,需要自己定义这个bean),
我们需要设置spring的扫描范围。保证这个类会被扫描到。
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.cgodo.daowole.action" />
// ...其他配置,如开启MVC的注解驱动
</beans>
视图解析
spring提供了几个视图解析类
BeanNameViewResolver BeanNameViewResolver会对返回的逻辑视图名,和applicationcontext中的bean id进行查找。
ContentNegotiatingViewResolver 代理一个或多个视图解析器。
FreeMarkerViewResolver 解析freemarker
InternalResourceViewResolver 会拼装前缀和后缀,再查找文件。如果找到,它来处理是自然的。
JasperReportsViewResolver jasper报表,会拼装前缀和后缀。
ResourceBundleViewResolver 根据属性文件,来查找物理视图。
TilesViewResolver 从titles定义的模板查找视图
UrlBasedViewResolver 其他视图解析的基类
VelocityLayoutViewResolver VelocityViewResolver的子类,支持布局。
VelocityViewResolver Velocity模板,会拼装前缀和后缀。
XmlViewResolver 从xml定义的文件中,查找返回的逻辑视图对应的物理视图。如
<bean id="viewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="location">
<value>/WEB-INF/xx-views.xml</value>
</property>
</bean>
其中location属性默认的是views.xml,可以自己进行设置,这里是xx-views.xml。下面是xx-views.xml文件的内容
<bean id="hello" class="org.springframework.web.servlet.view.JstlView">
<property name="url">
<value>/WEB-INF/jsp/hello.jsp</value>
</property>
</bean>
XsltViewResolver 解析基于XSLT的视图。拼装前缀和后缀。
InternalResourceViewResolver
InternalResourceViewResolver 会拼装前缀和后缀,再查找文件。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
默认情况下InternalResourceViewResolver 创建一个InternalResourceView对象,被将处理交给它。它支持简单的做dispatches(派发)而已。
而我们还可以修改viewclass
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
如上我们使用的viewClass是JstlView
JstlView不仅仅是派发
装载多个配置文件
正如前面说的,多个配置文件
处理输入参数
一中将请求参数映射为方法参数的方式
package com.cgodo.daowole.action;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.cgodo.daowole.model.Page;
@Controller
@RequestMapping("/test")
public class AnnotationAction {
@RequestMapping( { "/", "/**/home.do" })
public String home(
@RequestParam(value = "name", required = false) String username,
Model model) {
System.out.println("------------------------------");
System.out.println("userName is " + username);
System.out.println("------------------------------");
model.addAttribute(new Page<String>());
return "index";
}
}
请求中的参数name必须输入(required = true,如果没有@RequestParam,默认required = false,如果写了@RequestParam,默认required = true)
另外注意在类得上面有@RequestMapping("/test"),表示,只有再请求是资源位于/test下面才会处理
比如(应用是daowole)
127.0.0.1/daowole/abc/home.do
127.0.0.1/daowole/abc/test/home.do
127.0.0.1/daowole/
127.0.0.1/daowole/abc/
将不被处理,
而
127.0.0.1/daowole/test/home.do
127.0.0.1/daowole/test/
将被处理
如果去掉上面的类注解@RequestMapping("/test"),那么
127.0.0.1/daowole/abc/
127.0.0.1/daowole/test/
将不被处理
127.0.0.1/daowole/abc/home.do
127.0.0.1/daowole/abc/test/home.do
127.0.0.1/daowole/
127.0.0.1/daowole/test/home.do
将被处理
另外一个参数是Model,它是模型处理器。
model.addAttribute(new Page<String>());将根据传入的对象的类型,生成对应的attribute name,比如Page类型生成page,而Account生成account,AccountOther生成accountOther。
@RequestParam注解并非必须。当请求参数名和方法参数名字不相同的时候,才需要。如果不是用这个注解,那么将自动把请求参数名映射到对应的方法参数名。
另外我们的model是用的是Model接口,而不是Map<String, Object>,这是因为Model接口提供了更方便的功能。
package com.cgodo.daowole.action;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.cgodo.daowole.model.Page;
@Controller
@RequestMapping("/test")
public class AnnotationAction {
@RequestMapping( { "/", "/**/home.do" })
public String home(String pageSize, Model model, Page<String> page) {
System.out.println("------------------------------");
System.out.println("userName is " + pageSize);
System.out.println("------------------------------");
model.addAttribute(new Page<String>());
return "redirect:input.jsp";
}
}
上面的代码中,首先要知道Page类型里面有一个pageSize属性,另外我们方法中也定义了一个pageSize参数。这个时候如果我们请求中有pageSize参数,那么方法参数的pageSize属性和page对象里面的pageSize属性的值会同时被设置为这个请求参数的值。
另外需要看的是return "redirect:input.jsp";这里有一个redirect:前缀,表示重定向到input.jsp。
package com.cgodo.daowole.action;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import com.cgodo.daowole.model.Page;
@Controller
@RequestMapping("/my")
public class AnnotationAction {
@RequestMapping( { "/", "/**/home.do" })
public String home(String pageSize, Model model, Page<String> page) {
System.out.println("------------------------------");
System.out.println("userName is " + pageSize);
System.out.println("------------------------------");
model.addAttribute(new Page<String>());
return "redirect:input.jsp";
}
@RequestMapping( { "/{name}/{cask}" })
public String home(@PathVariable String name, String pageSize, Model model,
Page<String> page, @PathVariable String cask) {
System.out.println("------------------------------");
System.out.println("userName is " + pageSize);
System.out.println("------------------------------");
model.addAttribute(new Page<String>());
return "redirect:input.jsp";
}
}
看另一个方法home
@RequestMapping( { "/{name}/{cask}" })
{varName}会将/my/后面的路径转化成参数。
最后一个{varName}会自动去掉后缀。
@RequestMapping( { "/{name}/{cask}" })写了两个varName,因此必须存在两个目录级别(相对@RequestMapping("/my")而言)。由于只写了两级varName,因此也必须只有两级,三级的话就无效了。
PathVariable 默认根据参数名字和请求参数名自动映射(名字相同)。
可以根据自己的名字
@RequestMapping( { "/{test}/{cask}" })
public String home(@PathVariable("test") String name, String pageSize, Model model,
Page<String> page, @PathVariable String cask) {
System.out.println("------------------------------");
System.out.println("userName is " + pageSize);
System.out.println("------------------------------");
model.addAttribute(new Page<String>());
return "redirect:input.jsp";
}
它也支持ant路径风格
如果定义了类级别的RequestMapping路径参数风格,在方法级别那可以指定更详细的规则
package com.cgodo.daowole.action;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/owners/{ownerId}")
public class ControllerTest {
@RequestMapping(value = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String ownerId,
@PathVariable String petId, Model model) {
System.out.println();
}
@RequestMapping(value = "/", headers = "content-type=text/*")
public void get() {
System.out.println();
}
}
以上两个方法更定义了更详细的匹配规则。第一个方法只有在请求参数中有myParam=myValue才有效
http://127.0.0.1:8080/daowole/owners/liyixing/pets/a?myParam=myValue
文件上传
package com.cgodo.daowole.action;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import com.cgodo.daowole.model.Page;
@Controller
@RequestMapping("/my")
public class AnnotationAction {
@RequestMapping( { "/", "/**/home.do" })
public String home(String pageSize, Model model, Page<String> page,
BindingResult bindingResult,
@RequestParam(value = "image", required = false) MultipartFile image) {
System.out.println("------------------------------");
System.out.println("userName is " + pageSize);
System.out.println("------------------------------");
model.addAttribute(new Page<String>());
return "redirect:input.jsp";
}
@RequestMapping( { "/a/{name}/{cask}" })
public String home(@PathVariable String name, String pageSize, Model model,
Page<String> page, @PathVariable String cask) {
System.out.println("------------------------------");
System.out.println("userName is " + pageSize);
System.out.println("------------------------------");
model.addAttribute(new Page<String>());
return "redirect:input.jsp";
}
}
这里
@RequestParam(value = "image", required = false) MultipartFile image
发现@RequestParam去掉,就报错,
beans.BeanInstantiationException: Could not instantiate bean class [org.springframework.web.multipart.MultipartFile]:
Specified class is an interface
而留下这句就不会报错,不知道什么原因。。。
减少到最小
@RequestParam
到这种程度。
DispatcherServlet的不知道如何处理多部分
表单数据。我们需要一个multipart解析器来提取出的多重数据
DispatcherServlet的POST请求,以便它可以给我们的控制器。
要注册一个Spring multipart解析器,我们只需要声明一个bean,
实现了MultipartResolver接口。
spring提供了一个实现
CommonsMultipartResolver
<mvc:annotation-driven />后这个类会自动注入。当然我们如果要配置更多的属性,需要自己定义这个bean
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="5000000"></property>
</bean>
注意这里的bean id是固定的。spring会自动读取一个叫做multipartResolver的bean。如果叫做其他名字是无效的。
关于action的返回值和参数
经过@ RequestMapping注解定义的方法,这个方法的参数和返回值定义可以很灵活。下面说的参数可以定义任意数量。
1.可以在方法中定义Request和Response,类型可以是ServletRequest 或 HttpServletRequest.
response类似
2.Session对象,类型HttpSession.session是线程不安全的。可以在定义的AnnotationMethodHandlerAdapter中的参数synchronizeOnSession设置为true,来设置为线程安全的。
3.org.springframework.web.context.request.WebRequest类型或者org.springframework.web.context.request.NativeWebRequest类型的Servlet/Portlet API.
回应类似
4.java.util.Locale 获取当前请求的本地信息。
5.java.io.InputStream / java.io.Reader类型,可以获取到请求的输入流
6.java.io.OutputStream / java.io.Writer类型可以获取到回应的输出流。
7.java.security.Principal 当前的验证用户
8.在参数之前加入@PathVariable注解。来处理目录影射为参数值
9.在参数之前加入@RequestParam注解。来处理从参数影射为方法参数值
10.在参数之前加入@RequestHeader注解。用来将指定的请求头信息影射为方法的参数。
11.在参数之前加入@RequestBody注解。用来将指定的客户端发送过来的请求参数的数据格式转换成java实体
12.定义一个HttpEntity<?>类型的变量。将会把请求参数的head和请求内容转换成实体类。
13.java.util.Map / org.springframework.ui.Model /org.springframework.ui.ModelMap
这个是存放数据模型的。
14.命令,或表单对象的实体。会将请求参数转成实体。@InitBinder注解或者HandlerAdapter 定义的时候进行转换设置。
15.org.springframework.validation.Errors/org.springframework.validation.BindingResult验证结果。
16.org.springframework.web.bind.support.SessionStatus 会话状态
需要注意的是,Errors类型和BindingResult类型的参数的排序一定要有规则。
因为定义的实体模型可能有多个,spring会为每个实体创建org.springframework.validation.Errors/org.springframework.validation.BindingResult
如果定义的org.springframework.validation.Errors/org.springframework.validation.BindingResult类型排序不对应了,那么可能会无法得到你的预期结果
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet,
Model model, BindingResult result) { … }
这里的result对应的是model,而不是pet。
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet,
BindingResult result, Model model) { … }
这个是有效的。
控制器的返回结果
可以是
1.ModelAndView 包含了@ModelAttribute注解定义的key=@ModelAttribute("a")的value=模型的结果,视图名,命令对象。
2.Model对象,包含了视图名(RequestToViewNameTranslator类型),模型数据,命令对象,和ModelAttribute注解定义的模型。
3.Map对象,和Model类似。
4.View对象,包含了命令对象,@ModelAttribute注解定义的模型。可能还包含了Model对象。
5.String字符串。一个逻辑视图名。
6.void 当结果直接写入ServletResponse / HttpServletResponse。requestToViewNameTranslator将自动查找视图。
7.如果这个方法定义了@ResponseBody注解。那么会把返回值转换成这个数据格式,输出给客户端。
8.HttpEntity<?>类型或者ResponseEntity<?>,会将返回值转换成响应的head和响应内容。
9.其他任何类型。
绑定参数
@RequestParam
@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {
// ...
@RequestMapping(method = RequestMethod.GET)
public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
}
先看@RequestMapping,它指定了影射的关系。
在看SessionAttributes注解,其作用看后面内容。
@RequestParam("petId")
这里定义了从请求参数映射到方法参数的映射关系。直接写一个字符串表示将请求参数的petId映射为方法参数的第一个参数。
RquestBody注解
这个可以将http的请求 body转换成指定格式。
注意 http协议中,Request body只有在post格式提交的数据,才用户请求体(put,head等请求方式,很少使用,我们不考虑)。而get请求方式没有Request Body。详情请了解http协议
如上图
注意这里的转换和方法的参数名无关系。
编写如下内容
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>领钱</title>
</head>
<body>
领钱
<form action="test/body" method="post"><input
name="body" value="123" /> <input name="name" value="456" /><input
type="submit" /></form>
</body>
</html>
package com.cgodo.daowole.action;
import java.io.IOException;
import java.io.Writer;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/test")
public class ControllerTest {
@RequestMapping(value = "/pets/{petId}", params = "myParam=*")
public void findPet(@PathVariable String ownerId,
@PathVariable String petId, Model model) {
System.out.println();
}
@RequestMapping(value = "/body")
public void handle(@RequestBody() String host, Writer writer)
throws IOException {
writer.write(host);
}
}
访问
http://127.0.0.1:8081/daowole/index.jsp
并点击提交。请求体将被获得,我们还把请求体答应出去了。
这里的请求体转换是通过
HttpMessageConverter实现类来完成的。
基于注解的mvc使用DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter.(spring mvc 3)
AnnotationMethodHandlerAdapter提供了对RequestBody注解的支持。
提供实现的转换器
ByteArrayHttpMessageConverter 转换byte数组
StringHttpMessageConverter 转换成String
FormHttpMessageConverter 将请求体转换成一个MultiValueMap<String, String>
SourceHttpMessageConverter 转换javax.xml.transform.Source.我使用的时候报错,不知道是不是需要添加其他东西的支持
MarshallingHttpMessageConverter 转换成org.springframework.oxm下面的类
AnnotationMethodHandlerAdapter默认加上上面的内容的bean
另外,对于AnnotationMethodHandlerAdapter在处理requestbody注解之前,有request.getParameter(String name);方法的话,会破坏对post的body的处理,也就是会再它处理之前,把request的inputstream读取完毕了。(至于原因查看http协议)。如主题修改拦截器,本地化拦截器都会在它之前执行request.getParameter(String name);。那么就会是都最后得到的requestBody的结果为null。
@ResponseBody注解
这个注解可以放在一个方法的声明那,当方法返回时,将返回的内容直接写入到请求的客户端,而不是作为一个视图。
如
package com.cgodo.daowole.action;
import java.io.IOException;
import java.io.Writer;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/test")
public class ControllerTest {
@RequestMapping(value = "/pets/{petId}", params = "myParam=*")
public void findPet(@PathVariable String ownerId,
@PathVariable String petId, Model model) {
System.out.println();
}
@RequestMapping(value = "/body")
public void handle(@RequestBody() MultiValueMap<String, String> host,
Writer writer) throws IOException {
writer.write("");
}
@RequestMapping(value = "/responseBody")
@ResponseBody
public String responseBody() {
return "Hello World";
}
}
responseBody方法使用这个注解。
将直接输出hello world给客户端。
http://127.0.0.1:8081/daowole/test/responseBody
当然直接输出的内容可能会照成部分浏览器无法解析的问题。这要看浏览器的严格程度。
HttpEntity<?> ResponseEntity<?>
ResponseEntity是HttpEntity子类,专门用于处理响应的。
和@requestbody和@responsebody类似(既然类似,和他们一样需要post)
package com.cgodo.daowole.action;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/test")
public class ControllerTest {
@RequestMapping(value = "/pets/{petId}", params = "myParam=*")
public void findPet(@PathVariable String ownerId,
@PathVariable String petId, Model model) {
System.out.println();
}
@RequestMapping(value = "/body")
public void handle(@RequestBody() MultiValueMap<String, String> host,
Writer writer) throws IOException {
writer.write("");
}
@RequestMapping(value = "/responseBody")
@ResponseBody
public String responseBody() {
return "Hello World";
}
@RequestMapping("/entity")
public ResponseEntity<String> handle(HttpEntity<String> requestEntity)
throws UnsupportedEncodingException {
String requestHeader = requestEntity.getHeaders().getFirst(
"MyRequestHeader");
String requestBody = requestEntity.getBody();
// do something with request header and body
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders,
HttpStatus.CREATED);
}
}
结果如上图
@ModelAttribute注解
可以用在方法声明上面,或者方法参数声明的时候。注意声明在方法之前的@ModelAttribute注解(假设这个方法没有RequestMapping),将会在@RequestMapping定义了的方法之前执行一遍(如果@ModelAttribute和ModelAttribute和@RequestMapping不在同一个方法),然后再执行匹配的方法。如果@ModelAttribute和@RequestMapping在一个方法都存在。不会在其他方法之前执行。
@ModelAttribute("types")
public Collection<String> populatePetTypes() {
List<String> ss = new ArrayList<String>();
return ss;
}
@RequestMapping("/model")
public String processSubmit() {
return "index";
}
如上populatePetTypes方法是没有的RequestMapping,请求
http://127.0.0.1:8081/daowole/test/model,先执行
再执行
修改代码
@ModelAttribute("types")
@RequestMapping("/model1.do")
public Collection<String> populatePetTypes() {
List<String> ss = new ArrayList<String>();
return ss;
}
@RequestMapping("/model")
public String processSubmit() {
return "index";
}
访问http://127.0.0.1:8081/daowole/test/model
直接进入processSubmit,无需先执行populatePetTypes
http://127.0.0.1:8081/daowole/test/model1.do
进入populatePetTypes
@SessionAttributes注解
写在类级别的注解,定义一个session attributes,属性名字为SessionAttributes指定。可以指定多个(数组),也同时可以指定类型。
@Controller
@SessionAttributes( { "user" })
@RequestMapping("/test")
public class ControllerTest {
@RequestMapping("/session")
@ResponseBody
public String sessionIn(@ModelAttribute("user") User user) {
return "index";
}
@RequestMapping("/sessionOut")
@ResponseBody
public String sessionOut(HttpSession session) {
User user = (User) session.getAttribute("user");
if (user == null) {
user = new User();
user.setId(1);
session.setAttribute("user", user);
}
return "index";
}
}
访问sessionOut,然后再访问sessionIn,可以发现sessionIn的user可以访问到sessionOut设置的user
需要注意的是,@SessionAttributes注解在处理器处理的时候,会根据session.getAttributes的返回内容,来处理一次,也就是,当return = null (处理器是通过request.getSession(false),它不会创建session,如果session=null,直接return null),就抛出异常HttpSessionRequiredException异常。
而有时候,我们在session存放的对象,并非是必须存在的。解决这个问题有三个方法
1.这个时候,我们就可以通过
@ModelAttributes注解写在方法之前,并且这个方法没有@RequestMapping注解,那么这个方法会在之前处理的原理来解决。
注意,当写在方法上面的@ModelAttribute("user")指定的名字和@SessionAttributes( { "user" })指定的名字中,刚好重合的时候,make方法在一个session中只会被调用一次。
例子如下
@SessionAttributes( { "user" })
public class ControllerTest {
private int x = 0;
@ModelAttribute("user")
public Object make(HttpSession session) {
Object user = session.getAttribute("user");
if (user == null) {
user = new User();
}
return user;
}
@RequestMapping(value = { "/", "home.do", "index", "index.jsp",
"index.html", "index.htm" })
public String home(@ModelAttribute("user") User user, ModelMap model)
throws IOException {
List<String> messages = new ArrayList<String>();
messages.add("你没有登录,请先登录!");
messages.add("你输入的用户名不存在!");
messages.add("无效的账号");
model.put("messages", messages);
return "application/index";
}
上面的这种方法由于会依赖与HttpSession,这个时候这个类又重新依赖于servlet-api的架包了。不太好。
2.
我们可以换一种写法,使用ModelMap model,这个样子就能把对jee架包的依赖消除了。
@ModelAttribute("user")
public Object make(ModelMap model) {
Object user = model.get("user");
if (user == null) {
user = new User();
}
return user;
}
@CookieValue注解
@RequestMapping("/cookie")
@ResponseBody
public String cookie(@CookieValue("JSESSIONID") String sessionId) {
return sessionId;
}
@RequestHeader注解
@RequestMapping("/head")
@ResponseBody
public String head(@RequestHeader("Accept-Encoding") String head) {
return head;
}
3.
还有一种方式就是利用
WebBindingInitializer的功能,实现一个WebBindingInitializer,并且这个东西其实什么也不做,只是生成session的attributes。如
package com.cgodo.daowole.web.bind;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.WebRequest;
public class ClinicBindingInitializer implements WebBindingInitializer {
public void initBinder(WebDataBinder arg0, WebRequest arg1) {
//从session
Object o = arg1.getAttribute("code", ServletRequestAttributes.SCOPE_SESSION);
if( o == null) {
//放入session的值
arg1.setAttribute("code", new String(), 1);
}
}
}
WebBindingInitializer
通过InitBinder注解或者天加一个WebBindingInitializer.的实现类
@InitBinder写在@Controller的类中的方法上面,这个方法可以接受3个参数,当然这三个参数是灵活的。你需要定义几个都可以
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(
dateFormat, false));
}
@RequestMapping("/bind")
@ResponseBody
public String bind(Date date) {
return "test";
}
如上,定义中,binder.registerCustomEditor(Date.class, new CustomDateEditor(
dateFormat, false));指定了Date类型将由CustomDateEditor属性编辑器完成初始化工作。
WebBindingInitializer实现类实现方式
package com.cgodo.daowole.web.bind;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.WebRequest;
public class ClinicBindingInitializer implements WebBindingInitializer {
public void initBinder(WebDataBinder arg0, WebRequest arg1) {
System.out.println();
}
}
这个时候,就要重写AnnotationMethodHandlerAdapter bean的定义了。
<!-- <bean-->
<!-- class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">-->
<!-- <property name="cacheSeconds" value="0" />-->
<!-- <property name="webBindingInitializer">-->
<!-- <bean class="com.cgodo.daowole.web.bind.ClinicBindingInitializer" />-->
<!-- </property>-->
<!-- </bean>-->
注解驱动开启后会注册DefaultAnnotationHandlerMapping,
默认情况下它生成的默认配置包括了
interceptors 拦截器
defaultHandler 默认的hangler mapping
order 详细查看spring的order属性,Spring 将上下文中可用的映射进行排序,然后选用第一个和请求匹配的处理器。
alwaysUseFullPath 如果这个属性被设成true,Spring 将会使用绝对路径在当前的servlet context中寻找合适的处理器。 这个属性的默认值是false,在这种情况下,Spring会使用当前servlet context中的相对路径。 例如,如果一个servlet在servlet-mapping中用的值是/testing/*,当alwaysUseFullPath 设成true时, 处理器映射中的URL格式应该使用@RequestMapping("/testing/viewPage.html"),当这个属性设成false,同一个URL应该写成 @RequestMapping("/viewPage.html")
urlDecode这个属性的默认值是true,和2.5版本一样。 如果想比较编码后的路径,可以把这个属性设为false。 不过,需要注意的是,HttpServletRequest总是返回解码后的servlet路径, 与编码后的格式进行比较时可能不会匹配。
lazyInitHandlers:这个属性允许设置是否延迟singleton处理器的初始化工作(prototype处理器的初始化都是延迟的)。 这个属性的默认值是false。
(注意:最后三个属性只有org.springframework.web.servlet.handler.AbstractUrlHandlerMapping的子类才有。)
拦截器是基于HandlerInterceptor接口的实现完成的。定义拦截器后,需要重写DefaultAnnotationHandlerMapping bean的配置
如
<beans>
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<bean class="com.cgodo.daowole.web.interceptor.TimeBasedAccessInterceptor "/>
</property>
</bean>
<beans>
由于interceptors是一个list因此可以这么设置它的属性
<property name="interceptors">
<list>
<ref bean="officeHoursInterceptor"/>
</list>
</property>
注意,如果开启了<mvc:annotation-driven />,那么拦截器就不能按上面的方式定义,而要使用
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="siteLanguage" />
</bean>
</mvc:interceptors>
方式,否则没有用的。
package com.cgodo.daowole.web.interceptor;
import java.util.Calendar;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.cgodo.daowole.common.util.DateTImeUtil;
public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {
private int openingTime;
private int closingTime;
public void setOpeningTime(int openingTime) {
this.openingTime = openingTime;
}
public void setClosingTime(int closingTime) {
this.closingTime = closingTime;
}
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
Calendar cal = DateTImeUtil.getNowCalendar();
int hour = cal.get(Calendar.HOUR_OF_DAY);
if (openingTime <= hour && hour <= closingTime) {
return true;
} else {
response.sendRedirect("http://www.google.com.hk");
return false;
}
}
}
ContentNegotiatingViewResolver:
很重要的一个特性即是同一资源,多种表述.也即如下面描述的三种方式:
1.使用http request header: Accept
Xml代码
GET /user/123 HTTP/1.1
Accept: application/xml //将返回xml格式数据
GET /user/123 HTTP/1.1
Accept: application/json //将返回json格式数据
2.使用扩展名
Html代码
/user/123.xml 将返回xml格式数据
/user/123.json 将返回json格式数据
/user/123.html 将返回html格式数据
3.使用参数
Html代码
/user/123?format=xml //将返回xml数据
/user/123?format=json //将返回json数据
使用Accept header:
这一种为教科书中通常描述的一种,理想中这种方式也是最好的,但如果你的资源要给用户直接通过浏览器访问(即html展现),那么由于浏览器的差异,发送上来的Accept Header头将是不一样的. 将导致服务器不知要返回什么格式的数据给你. 下面是浏览器的Accept Header
Html代码
chrome:
Accept:application/xml,application/xhtml+xml,textml;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
firefox:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
spring完成内容协商(content negotiation)的工作是由ContentNegotiatingViewResolver来完成的
locale
是由LocaleResolver完成的。
DispatcherServlet会查找locale解析器。我们可以通过RequestContext.getLocale()方法获取本地化。
LocaleResolver的实现有
AcceptHeaderLocaleResolver
CookieLocaleResolver
SessionLocaleResolver
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="cookieName" value="clientlanguage"/>
<property name="cookieMaxAge" value="100000">
</bean>
CookieLocaleResolver的属性
cookieName,默认是classname + LOCALE,由于是
CookieLocaleResolver 还有一个属性是defaultLocale。如果没有个它设置默认值,那么在调用它的determineDefaultLocale方法的时候,会有判断,如果是null,就从request.getLocale();获取。
org.springframework.web.servlet.i18n.CookieLocaleResolver处理的,所以叫org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE
cookieMaxAge cookie的最大时间 默认 Integer.MAX_INT
cookiePath cookie有效路径。 默认/
另外还提供了一个请求参数拦截器,会根据请求参数,来修改对应的本地化解析器的本地化信息
LocaleChangeInterceptor
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="siteLanguage" />
</bean>
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver" />
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<ref bean="localeChangeInterceptor" />
</list>
</property>
</bean>
如果开了
<mvc:annotation-driven />
那么配置应该是
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="siteLanguage" />
</bean>
</mvc:interceptors>
<bean id="lo
推荐阅读
-
十、Spring boot 简单优雅的整合 Swagger2
-
使用Spring Boot和AspectJ实现方法跟踪基础结构
-
spring boot 一个项目启动多个实例
-
Mybatis整合spring(适合小白)
-
spring boot 2 集成JWT实现api接口认证
-
Spring Cloud系列-Zuul网关集成JWT身份验证
-
Jenkins + Docker + dockerfile-maven-plugin + Harbor CI/CD spring-boot项目的最轻量级配置
-
面试必问:Spring循环依赖的三种方式
-
Spring Boot 的静态资源处理
-
带你使用Visual Studio 2019创建一个MVC Web应用