1、SpringMVC 和 Struts2 对比
①、SpringMVC 的入口是 Servlet,而Struts2是Filter
②、SpringMVC会稍微比Struts2快些,SpringMVC是基于方法设计的,而Struts2是基于类设计的,每次发一次请求都会实例一个Action.
③、SpringMVC使用更加简洁,开发效率比Struts2高。支持JSR303,处理ajax的请求更方便
④、Struts2的OGNL表达式使页面的开发效率相比SpringMVC更高些。
2、SpringMVC 和Spring 整合的问题
是否还需要 整合spring?
建议需要:通常情况下,类似于数据源,事务,整合其他框架都是放在Spring 的配置文件中
(而不是放 在 SpringMVC 的 配 置文件中)
实际上放入Spring配置文件对应的IOC容器中的还有Service 和 Dao.
问题:若spring 的 IOC容器和SpringMVC 的 IOC 容器扫描的包有重合的部分,就会导致有的bean会被创建2次
解决:
1、使spring的IOC容器扫描的包和SpringMVC 的 IOC 容器扫描的包分开,没有重合的部分,
可是在实际开发中,如果按模块建包,则无法满足该情况
2、使用context:component-scan标签的context:exclude-filter、context:include-filter
节点来规定只能扫描的注解
<context:component-scan base-package="com.test" use-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
springMVC 的 IOC 容器中的bean 可以来引用spring IOC容器中的bean,反之,则不行
即 spring IOC容器中的bean不能引用springMVC IOC容器中的bean。
因为 springMVC容器和spring容器有父子关系,spring容器为父容器,springMVC为子容器
3、SpringMVC工作流程
3、springMVC配置
在web.xml中配置DispatcherServlet
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcherServlet-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<init-param>标签:
springMVC配置文件默认在WEB-INF下,如果需要放在src下, 则需要在web.xml 配置DispatcherServlet中配置
用<init-param>标签进行配置。
注意:如果是用maven构建的项目,那么src目录是:src/main/resources(第一次用maven,在此折腾半天,才发现这个问题)
<load-on-startup>1</load-on-startup>:表示在WEB容器启动时就实例化DispatcherServlet,而不是等到该servlet被选择时。正数数字越小,优先级越高。
<url-pattern>/</url-pattern>:表示拦截所有的请求
如果用到REST风格URL,还需在web.xml中配置HidddenHttpMethodFilter
<!-- 配置HiddenHttpMethodFilter 作用可以把post请求转为delete或者put请求 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
然后配置springMVC配置文件
注意该配置文件的名字格式为 <servlet-name>-servlet.xml
<!-- 启用spring mvc 注解 -->
<context:annotation-config></context:annotation-config>
<!-- 需要扫描的spring控制类 -->
<context:component-scan base-package="com.test"></context:component-scan>
<context:component-scan base-package="com.cjl"></context:component-scan>
<!-- 视图解析器配置:如何把handler方法返回的值解析为实际的物理视图(该解析器用于JSP页面) -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/views/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<!-- 配置视图解析器BeanNameViewResolver:使用视图的名字解析视图 -->
<!-- 通过order属性来定义视图解析器的优先级,value越小优先级越高 -->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="100"></property>
</bean>
<!--配置直接跳转转发的页面 而无需经过Handler的方法 -->
<mvc:view-controller path="/hello" view-name="hello"/>
<!-- 直接跳转页码,不经过handler,在实际开发中,通常需要配置 mvc:annotation-driven 标签 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 静态资源 -->
<!--
default-servlet-handler 将在springmvc上下文中定义一个DefaultServletHttpRequestHandler
它会地进入DispatcherServlet的请求进行筛查,如果发现是没有映射的请求,就将改请求交由WEB应用服务器默认的
servlet 处理。如果不是静态资源的请求,才有DispatcherServlet继续处理
一般WEB应用服务器默认的servlet的名称都是default
若使用的WEB服务器的默认servlet名称不是default,则需要通过 default-servlet-name 属性显示指定
-->
<mvc:default-servlet-handler/>
<!--
配置多个拦截器,那么多个拦截器的执行顺序是什么?
[FirstInterceptor] preHandle
[SecondInterceptor] preHandle
listAllEmployees
[SecondInterceptor] postHandle
[FirstInterceptor] postHandle
[SecondInterceptor] afterCompletion
[FirstInterceptor] afterCompletion
preHandle 按正序执行
postHandle、afterCompletion 按倒序执行
若SecondInterceptor preHandle 返回false,则执行顺如下:
[FirstInterceptor] preHandle
[SecondInterceptor] preHandle
[FirstInterceptor] afterCompletion
-->
<!--配置拦截器 -->
<mvc:interceptors>
<!--配置自定义拦截器 -->
<bean class="com.test.interceptor.FirstInterceptor"></bean>
<!-- 配置拦截器作用的路径-->
<mvc:interceptor>
<mvc:mapping path="/listAllEmployees"/>
<!--
<mvc:exclude-mapping path="/emp"/> -->
<bean class="com.test.interceptor.SecondInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
<!--配置拦截器 -->
InternalResourceViewResolver:
①、级别默认最大,所以如果配置了其他视图解析器,都比它先执行,其他视图解析器,则按order顺序从小的数字开始执行。
②、prefix,suffix:该视图解析器的解析出该跳转的页面是:prefix+returnViewName+suffix,
我的例子即是:/WEB-INF/views/xx.jsp
③、实际开发中 <mvc:view-controller> <mvc:annotation-driver> 要一起配置
④、<mvc:default-servlet-handler> 用于静态资源映射
4、多个拦截器执行顺序
[FirstInterceptor] preHandle
[SecondInterceptor] preHandle
listAllEmployees //目标方法
[SecondInterceptor] postHandle
[FirstInterceptor] postHandle
[SecondInterceptor] afterCompletion
[FirstInterceptor] afterCompletion
preHandle 按正序执行
postHandle、afterCompletion 按倒序执行
若SecondInterceptor preHandle 返回false,则执行顺如下:
[FirstInterceptor] preHandle
[SecondInterceptor] preHandle
[FirstInterceptor] afterCompletion
5、Rest风格的URL
新增:/order post
修改:/order/1 put
删除:/order/1 delete
获取:/order/1 get
如何发送put和delete请求呢?
①、先在springMVC配置文件中配置HiddenHttpMethodFilter
②、需要发送post请求
③、在发送post请求时,携带一个name=“_method”的隐藏域,值为PUT或者DELETE
如何得到请求中的参数?
在目标方法中用@PathVariable注解获取参数
以delete为例,示例如下:
<form action="testRest/1" method="post">
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="testRest Delete"/>
</form>
@RequestMapping(value="/testRest/{id}",method=RequestMethod.DELETE)
public String testRestDelete(@PathVariable Integer id)
{
System.out.println("testRest DELETE:"+id);
return "hello";
}
6、@ModelAttribute注解
/**
* 1、有@ModelAttribute 修饰的方法,会在每个目标方法执行之前被springMVC调用
* 2、@ModelAttribute 注解也可以来修饰目标方法POJO类型的入参,其value 属性值有如下作用:
* 1)、spirngMVC 会使用value属性值在implicitModel 中查找对应的对象,若存在则会直接换入到目标方法的入参中
* 2)、springMVC会以value为key,POJO类型的对象为value,存入到request中。
*/
@ModelAttribute
public void geteUser(@RequestParam(value="id",required=false) Integer id,Map<String,Object> map)
{
System.out.println("testModelAttribute method");
if(id != null)
{
//模拟从数据库取出一个对象
User user = new User("1", "tom", "123456");
System.out.println("从数据库取出一个对象:"+user);
map.put("abc", user);
}
}
/**
* 运行流程:
* 1、执行 @ModelAttribute 注解修饰的方法,从数据库取出对象,把对象放入map中,键为user
* 2、springMVC 从map中取出user对象,并把表单中的请求参数赋给该user对象的对应属性
* 3、springMVC 把上述对象传入目标方法的参数
* 注意:在 @ModelAttribute修饰的方法中,放入到map时的键需要和目标参数的第一个字母小写的字符串一致
*
* springMVC确定目标方法POJO类型入参的过程
* 1、确定一个key
* 1)、若目标方法的POJO类型的参数没有使用 @ModelAttribute 作为参数,则key为POJO类名第一个字母小写
* 2)、若使用了 @ModelAttribute来修饰,则key为 @ModelAttribute 注解的value属性值
*
* 2、在implicitModel中查找key对应的对象,若存,则作为入参传入
* 1)、若在 @ModelAttribute 标记的方法中在Map中保存过,且key和1确定的key一致,则会获取到
*
* 3、若implicitModel中不存在key对应的对象,则检查当前的Handler是否使用了 @SessionAttributes 注解修饰,
* 若使用了改注解,且 @SessionAttribute 注解的value属性值也包含了key,则会从HttpSession中来获取key所对应
* 的value值,若存在则直接传入到目标方法的入参中,若不存在则抛出异常
* 4、若Handler没有表示 @SessionAttributes注解 或 @SessionAttributes 注解的value值中不包含key,则会通过反射来创建POJO类型的参数,
* 传入为目标方法的参数
* 5、springMVC会把key 和 POJO类型的对象 保存到implicitModel中,进而会保存到request中。
*
*
* 源码分析:
* 1、首先调用@ModelAttribute 注解修饰的方法,实际上把@ModelAttribute方法中的map中的数据放在了 implicitmodel中
* 2、解析请求处理器的目标参数,实际上该目标参数来自于 WebDataBinder对象的target属性
* (1)、创建WebDataBinder 对象;
* ①、确定 objectname 属性:若传入的attrName 属性值为“”,则objectName 为类名第一个字母小写
* 注意:attrName 若目标方法的pojo属性使用了@ModelAttribute来修饰,则attrName 值即为@ModelAttribute的value 属性值
*
* ②、确定target 属性:
* > 在implicitModel 中查找attrName 对应的属性值,若存在,ok
* > *若不存在则验证当前Handler是否使用了@SessionAttribute进行修饰,若使用了,则 尝试从session中获取attrName 所对应的属性值,
* 若session中没有对应的属性值,则抛出异常
* > 若Handler没有使用@SessionAttribute进行修饰,或@SessionAttributes 中没有使用value值指定的key和attrName 相匹配,
* 则通过反射创建了POJO对象
*
* (2)、springMVC把表单请求的参数赋给了WebDataBinder 的 target 对应的属性
* (3)、*springMVC会把WebDataBinder的attrName 和target 给到 implicitModel,进而传到request 域对象中
* (4)、把WebDataBinder 的 target作为参数传递给目标方法的入参
*/
@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute(value="abc") User user)
{
System.out.println("修改:"+user);
return HELLO;
}
注意:①、@ModelAttribute 修饰的方法会在每个目标方法被调用之前调用。
②、如上例,如果 testModelAttribute 方法中,没有 @ModelAttribute(value="abc"),则spirngMVC默认以该POJO类名的第一个字母小写为key;如果有@ModelAttribute(value="abc"),且@ModelAttribute 修饰的方法中需要 map.put("abc", user);
这时,就不以POJO类名第一个字母小写为key,以@ModelAttribute(value="abc") value为key。
7、使用POJO对象绑定请求参数
使用POJO对象绑定请求参数,springMVC会按请求参数名和POJO属性名进行自动匹配,自动为该对象填充属性值,支持级联属性。
8、处理模型数据
SpringMVC提供了以下几种途径输出模型数据:
1)、ModelAndView:处理方法返回值类型为ModelAndView时,方法体即可通过该对象添加模型数据。
2)、Map 及 Model:入参为 org.spirngframework.ui.Model、org.spriingframework.ui.ModelMap 或者 java.uti.Map 时,处理方法返回时,Map 中的数据会自动添加到模型中。
3)、@SessionAttributes:将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享这个属性。
@SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中。
--@SessionAttributes(types=User.class) 会将隐含模型中所有类型为User.class 的属性添加到会话中。
--@SessionAttributes(value={"user1","user2"})
--@SessionAttributes(types={User.class,Dept.class})
--@SessionAttributes(value={"user1","user2"},types={Dept.class})
4)、@ModelAttribute:方法入参标注该注解后,入参的对象就会放到数据模型中。属性暂存到HttpRequest中。
9、 @ModelAttribute @SessionAttributes 标注的属性,前台如何取?
request user:${requestScope.user }
session user: ${sessionScope.user }
注意:JSP页面EL表达式开关必须打开,否则页面不会正常显示,如下:
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8" isELIgnored="false"%>
isELIgnored:是否忽略EL表达式;
如果为true,则${}中的表达式会原样输出,不会进行计算;
如果是false,则会计算${}中的表达式;
番外:目标方法中map中的值,页面如何取?
time:${requestScope.time}
10、处理静态资源
优雅的REST风格的资源URL不希望带有.html 或 .do等后缀。
若将DispatcherServlet 请求映射配置为/,则SpringMVC将会捕获WEB容器的所有请求,包括静态资源的请求,SpringMVC会将他们当成一个普通请求处理,因此找不到对应的处理器将导致错误。
可以在SpringMVC的配置中配置<mvc:default-seervlet-handler/>的方式解决静态资源的问题
<mvc:default-servlet-handler/>会在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler,它会对进入DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由WEB应用服务器默认的Servlet处理,如果不是静态资源,才由DispatcherServlet继续处理。
一般WEB应用服务器默认的Servlet的名称都是default。若所使用的WEB服务器的默认Servlet名称不是default,则需要通过
default-servlet-name属性显示指定
11、数据绑定流程
1)、SpringMVC框架将ServletRequest 对象及目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象
2)、DataBinder 调用装配在SpringMVC上下文中的ConversionService组件进行 数据类型转换、数据格式化 工作。将Servlet中的请求信息填充到入参对象中。
3)、调用Validator 组件对已经绑定了请求信息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData对象。
4)、SpringMVC抽取BindingResult中的入参对象和校验错误对象,将它们赋给处理方法的响应入参。
12、springMVC的各种注解
1)、@Controller
@Controller注解的类表示该类是个控制器类。
2)、@PathVariable
@PathVariable 绑定URL占位符的入参。带占位符的URL是spring3.0新增的功能,该功能在springMVC向REST目标挺进发展过程中具有里程碑的意义。
通过 @PathVariable 可以将URL中占位符参数绑定到控制器处理方法的入参中:URL中的{xxx}占位符可以通过@PathVariable("xxx") 绑定到操作方法的入参中。
3)、@ResquestMapping
使用 @ResquestMapping 映射请求
@ResquestMapping 可以用到类上,也可以用到方法上,如果类上加上了该注解,那么该control下的所有方法的mapping前面都会自动加上类上的mapping。例如:类上@ResquestMapping(value="/aaa"),方法上@ResquestMapping(value="/bbb.htm"),那么请求bbb.htm,实际的URL是:/aaa/bbb.htm
①、Ant 风格资源地址支持3中匹配符:
--?:匹配文件名中的一个字符
--* :匹配文件名中的任意字符
--**:匹配多层路径
②、@RequestMapping就支持Ant风格的URL:
-- /user/*/createUser:匹配
/user/aaa/createUser、/user/bbb/createUser 等URL
--/user/**/cereateUser:匹配
/user/createUser 、/user/aaa/bbb/createUser 等 URL
4)、@SessionAttributes
若希望在多个请求之间公用某个模型属性数据,则可以在控制器类上 标注一个@SessionAttributes,SpringMVC将在模型中对应的属性暂存到HttpSession中。
5)、@ModelAttribute
--在方法定义上使用@ModelAttribute注解:SpringMVC在调用目标处理方法前,会先逐个调用在方法上标注了@ModelAttribute的方法
--在方法 的入参前使用 @ModelAttribute注解:
①、可以从隐含对象中获取隐含的模型数据中获取对象,在将请求参数绑定到对象中,再传入入参。
②、将方法入参对象添加到模型中。
6)、@AutoWired
它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过@AutoWired的使用来消除 set,get 方法。
7)、@ReponseBody
用来修饰Controller的目标方法,通过适当的HttpMessageConverter转换器把返回值由String转换为指定的格式,放在Response的body区。如转换为JSON
8)、@RequestHeader
使用@RequestHeader 绑定请求报头的属性值。
请求头包含了若干个属性,服务器可据此获知客户端的信息,通过@RequestHeader 即可将请求头中的属性值绑定到处理方法的入参中。
9)、@CookieValue
@CookieValue 可让处理方法入参绑定某个Cookie值
10)、@RequestParam
在处理方法入参处使用 @RequestParam 可以把请求参数传递给请求方法。
--value:参数名
--required:是否必须。默认为true,表示请求参数中必须包含对应的参数,若不存在,将抛出异常。
11)、@InitBinder
由 @InitBinder 标识的方式,可以对WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成有表单字段到JavaBean属性的绑定。
@InitBinder 方法不能有返回值,它必须声明为void
@InitBinder 方法的参数通常是WebDataBinder
12)、@Repository
用来标注数据访问组件,即Dao组件
13)、@Component
用于把普通POJO类融入Spring容器,相当于配置文件中的<bean id="" class="" />
泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。
14)、@Service
用于标注服务层,主要用来进行业务逻辑的处理
15)、@ExceptionHandler
用来修饰方法,出现异常时,执行该方法
16)、@ControllerAdvice+@ExceptionHandler
使一个Contoller成为全局的异常处理类
13、
Neither BindingResult nor plain target object for bean name 'command' available as request attribute
出错场景:经过一个action跳转到新增用户页面
@RequestMapping(value="/input",method=RequestMethod.GET)
public String input()
{
return "input";
}
原因是:用<form:form>标签,springMVC解析JSP之前,先去attribute找该form对应的POJO对象,如果没有找到,则用默认的对象command,然后也没找到,所以报错。
解决办法:
①、在JSP页面增加 <jsp:useBean id="command" class="com.cjl.beans.User" scope="request"></jsp:useBean> 引入该bean
②、在响应的controller中加入该代码 map.put("user", new User());,
然后在JSP页面,<form:form> 该标签中加入modelAttribute="user" 即可。