本来地址:springMVC干货系列:从零搭建springMVC+mybatis(二):springMVC原理解析及经常使用注解
博客地址:tengj.top/php
上篇文章介绍了maven WEB 项目的搭建,基本的配置文件也都贴出来了,今天就来介绍下SpringMVC的工做原理以及工做中经常使用的注解。为之后开发打下坚实的基础。html
SpringMVC就是经过DispatcherServlet将一堆组件串联起来的Web框架。前端
Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,能够选择是使用内置的 Spring Web 框架仍是 Struts 这样的 Web 框架。经过策略接口,Spring 框架是高度可配置的,并且包含多种视图技术,例如 JavaServer Pages(JSP)技术、Velocity、Tiles、iText 和 POI。Spring MVC 框架并不知道使用的视图,因此不会强迫您只使用 JSP 技术。
Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制java
DispatcherServlet接口:
Spring提供的前端控制器,全部的请求都有通过它来统一分发。在DispatcherServlet将请求分发给Spring Controller以前,须要借助于Spring提供的HandlerMapping定位到具体的Controller。web
HandlerMapping接口:
可以完成客户请求到Controller映射。spring
Controller接口:
须要为并发用户处理上述请求,所以实现Controller接口时,必须保证线程安全而且可重用。安全
Controller将处理用户请求,这和Struts Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View)。cookie
从宏观角度考虑,DispatcherServlet是整个Web应用的控制器;从微观考虑,Controller是单个Http请求处理过程当中的控制器,而ModelAndView是Http请求过程当中返回的模型(Model)和视图(View)。session
ViewResolver接口:
Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。mybatis
客户端请求提交到DispatcherServlet
由DispatcherServlet控制器查询一个或多个HandlerMapping,找处处理请求的Controller
DispatcherServlet将请求提交到Controller
Controller调用业务逻辑处理后,返回ModelAndView
DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
视图负责将结果显示到客户端
DispatcherServlet是整个Spring MVC的核心。它负责接收HTTP请求组织协调Spring MVC的各个组成部分。其主要工做有如下三项:
结合项目理解:
1.你们由上面原理也看明白了,DispatcherServlet是整个Spring MVC的核心,SpringMVC全部的请求都会经过这个前端控制器。它配置的地方是在web.xml里面,配置以下:
<servlet>
<servlet-name>springmvctouchbaidu</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>复制代码
配置的时候还指明了contextConfigLocation,这样就会去加载这个applicationContext.xml了。
2.原理第2点中由DispatcherServlet控制器查询一个或多个HandlerMapping,找处处理请求的Controller。这里实际上是经过在applicationContext-mvc.xml配置了扫描路径以及开启了注解驱动来实现的。
applicationContext-mvc.xml中的配置:
<context:component-scan base-package="com.tengj.demo"/>复制代码
context:component-scan说明了要扫描com.tengj.demo这个包下全部的类。这里要注意一下,你们之后开发中有用到注解的类必定都要在这个demo包下,否则就会抛异常的。
加载了扫描路径后,还要开启注解驱动,这样才能认到代码中使用到的注解,好比@Controller这个注解。
<mvc:annotation-driven />复制代码
3.ViewResoler视图解析器对应配置里面的
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>复制代码
这样,当controller中方法返回的是
return "index";复制代码
的时候,实际是指向了/WEB-INF/pages/index.jsp这个页面。
当咱们使用了自动扫描+注解的方式后,就不须要在applicationContext-mvc.xml里面配置类的bean了,要引用类直接在成员变量上面加行注解,set/get方法也省了。超级方便,下面就列出常规开发中经常使用的注解。
@Component
是全部受Spring 管理组件的通用形式,@Component注解能够放在类的头上,@Component不推荐使用。
@Controller对应表现层的Bean,也就是Action,例如:
@Controller
public class UserController {
……
}复制代码
使用@Controller注解标识UserController以后,就表示要把UserController交给Spring容器管理,在Spring容器中会存在一个名字为"userController"的action,这个名字是根据UserController类名来取的。注意:若是@Controller不指定其value【@Controller】,则默认的bean名字为这个类的类名首字母小写,若是指定value【@Controller(value="UserController")】或者【@Controller("UserController")】,则使用value做为bean的名字。
@Service对应的是业务层Bean,例如:
@Service("userService")
public class UserServiceImpl implements UserService{
………
}复制代码
@Service("userService")注解是告诉Spring,当Spring要建立UserServiceImpl的的实例时,bean的名字必须叫作"userService",这样当Action须要使用UserServiceImpl的的实例时,就能够由Spring建立好的"userService",而后注入给Action:在Action只须要声明一个名字叫“userService”的变量来接收由Spring注入的"userService"便可,具体代码以下:
//注入userService
@Resource(name="userService")
UserService userService;复制代码
注意:在UserController声明的“userService”变量的类型必须是“UserServiceImpl”或者是其父类“UserService”,不然因为类型不一致而没法注入,因为UserController中的声明的“userService”变量使用了@Resource注解去标注,而且指明了其name = "userService",这就等于告诉Spring,说我UserController要实例化一个“userService”,你Spring快点帮我实例化好,而后给我,当Spring看到userService变量上的@Resource的注解时,根据其指明的name属性能够知道,UserController中须要用到一个UserServiceImpl的实例,此时Spring就会把本身建立好的名字叫作"userService"的UserServiceImpl的实例注入给UserController中的“userService”变量,帮助UserController完成userService的实例化,这样在UserController中就不用经过“UserService userService = new UserServiceImpl();”这种最原始的方式去实例化userService了。
若是没有Spring,那么当UserController须要使用UserServiceImpl时,必须经过“UserService userService = new UserServiceImpl();”主动去建立实例对象,但使用了Spring以后,UserController要使用UserServiceImpl时,就不用主动去建立UserServiceImpl的实例了,建立UserServiceImpl实例已经交给Spring来作了,Spring把建立好的UserServiceImpl实例给UserController,UserController拿到就能够直接用了。
UserController由原来的主动建立UserServiceImpl实例后就能够立刻使用,变成了被动等待由Spring建立好UserServiceImpl实例以后再注入给UserController,UserController才可以使用。这说明UserController对“UserServiceImpl”类的“控制权”已经被“反转”了,原来主动权在本身手上,本身要使用“UserServiceImpl”类的实例,本身主动去new一个出来立刻就可使用了,但如今本身不能主动去new“UserServiceImpl”类的实例,new“UserServiceImpl”类的实例的权力已经被Spring拿走了,只有Spring才可以new“UserServiceImpl”类的实例,而UserController只能等Spring建立好“UserServiceImpl”类的实例后,再“恳求”Spring把建立好的“UserServiceImpl”类的实例给他,这样他才可以使用“UserServiceImpl”,这就是Spring核心思想“控制反转”,也叫“依赖注入”。
“依赖注入”也很好理解,UserController须要使用UserServiceImpl干活,那么就是对UserServiceImpl产生了依赖,Spring把Acion须要依赖的UserServiceImpl注入(也就是“给”)给UserController,这就是所谓的“依赖注入”。对UserController而言,UserController依赖什么东西,就请求Spring注入给他,对Spring而言,UserController须要什么,Spring就主动注入给他。
@Repository对应数据访问层Bean ,例如:
@Repository(value="userDao")
public class UserDao {
………
}复制代码
@Repository(value="userDao")注解是告诉Spring,让Spring建立一个名字叫“userDao”的UserDao实例。
当Service须要使用Spring建立的名字叫“userDao”的UserDao实例时,就可使用@Resource(name = "userDao")注解告诉Spring,Spring把建立好的userDao注入给Service便可。
// 注入userDao
@Resource(name = "userDao")
private UserDao userDao;复制代码
上面介绍中Controller中注入userService或者 Service层里面注入dao都是用@Resource标签,其实也可使用@Autowired来替代,可是建议使用@Resource。下面说说这2者的区别:
@Autowired
@Qualifier("baseDao")
private BaseDao baseDao;复制代码
@Resource(name="baseDao")
private BaseDao baseDao;复制代码
5.之因此推荐使用@Resource,由于这个注解是属于J2EE的,减小了与spring的耦合。这样代码看起就比较优雅。SpringMVC使用@RequestMapping注解为控制器制定能够处理哪些URL请求
在控制器的类定义及方法定义处均可以标注
@Controller
@RequestMapping(value="/test")
public class UserController{
@RequestMapping(value="/view",method = RequestMethod.GET)
public String index(){
System.out.println("进来了");
return "index";
}
}复制代码
上面这样,只要地址访问http://localhost:8080/SpringMVCMybatis/test/view 就能进入这个index方法了,其中使用method属性来指定请求是get仍是post。 带占位符的URL是Spring3.0新增的功能,该功能在SpringMVC向REST目标挺进发展过程当中具备里程碑的意义
经过@PathVariable能够将URL中占位符参数绑定到控制器处理方法的入参中:URL中的{xxx}占位符能够经过@PathVariable("xxx")绑定到操做方法入参中。
例子:
/** * @RequestMapping 能够来映射URL中的占位符到目标方法的参数中 * @param id * @return */
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") String id){
System.out.println("testPathVariable id="+id);
return "index";
}复制代码
@RequestMapping ( "requestParam" )
public String testRequestParam( @RequestParam(required=false) String name, @RequestParam ( "age" ) int age) {
return "requestParam" ;
}复制代码
在上面代码中利用@RequestParam 从HttpServletRequest 中绑定了参数name 到控制器方法参数name ,绑定了参数age 到控制器方法参数age 。值得注意的是和@PathVariable 同样,当你没有明确指定从request 中取哪一个参数时,Spring 在代码是debug 编译的状况下会默认取更方法参数同名的参数,若是不是debug 编译的就会报错。此外,当须要从request 中绑定的参数和方法的参数名不相同的时候,也须要在@RequestParam 中明确指出是要绑定哪一个参数。在上面的代码中若是我访问/requestParam.do?name=hello&age=1 则Spring 将会把request请求参数name 的值hello 赋给对应的处理方法参数name ,把参数age 的值1 赋给对应的处理方法参数age 。
在@RequestParam 中除了指定绑定哪一个参数的属性value 以外,还有一个属性required ,它表示所指定的参数是否必须在request 属性中存在,默认是true ,表示必须存在,当不存在时就会报错。在上面代码中咱们指定了参数name 的required 的属性为false ,而没有指定age 的required 属性,这时候若是咱们访问/requestParam.do而没有传递参数的时候,系统就会抛出异常,由于age 参数是必须存在的,而咱们没有指定。而若是咱们访问/requestParam.do?age=1 的时候就能够正常访问,由于咱们传递了必须的参数age ,而参数name 是非必须的,不传递也能够。
@RequestMapping ( "cookieValue" )
public String testCookieValue( @CookieValue ( "hello" ) String cookieValue, @CookieValue String hello) {
System. out .println(cookieValue + "-----------" + hello);
return "cookieValue" ;
}复制代码
在上面的代码中咱们使用@CookieValue 绑定了cookie 的值到方法参数上。上面一共绑定了两个参数,一个是明确指定要绑定的是名称为hello 的cookie 的值,一个是没有指定。使用没有指定的形式的规则和@PathVariable、@RequestParam 的规则是同样的,即在debug 编译模式下将自动获取跟方法参数名同名的cookie 值。
@RequestMapping ( "testRequestHeader" )
public String testRequestHeader( @RequestHeader ( "Host" ) String hostAddr, @RequestHeader String Host, @RequestHeader String host ) {
System. out .println(hostAddr + "-----" + Host + "-----" + host );
return "requestHeader" ;
}复制代码
在上面的代码中咱们使用了 @RequestHeader 绑定了 HttpServletRequest 请求头 host 到Controller 的方法参数。上面方法的三个参数都将会赋予同一个值,由此咱们能够知道在绑定请求头参数到方法参数的时候规则和 @PathVariable 、 @RequestParam 以及 @CookieValue 是同样的,即没有指定绑定哪一个参数到方法参数的时候,在 debug 编译模式下将使用方法参数名做为须要绑定的参数。可是有一点 @RequestHeader 跟另外三种绑定方式是不同的,那就是在使用 @RequestHeader 的时候是大小写不敏感的,即 @RequestHeader(“Host”) 和 @RequestHeader(“host”) 绑定的都是 Host 头信息。记住在 @PathVariable 、 @RequestParam 和 @CookieValue 中都是大小写敏感的。
在RequestMapping 中除了指定请求路径value 属性外,还有其余的属性能够指定,如params 、method 和headers 。这样属性均可以用于缩小请求的映射范围。
1.params属性
@RequestMapping (value= "testParams" , params={ "param1=value1" , "param2" , "!param3" })
public String testParams() {
System. out .println( "test Params..........." );
return "testParams" ;
}复制代码
在上面的代码中咱们用@RequestMapping 的params 属性指定了三个参数,这些参数都是针对请求参数而言的,它们分别表示参数param1 的值必须等于value1 ,参数param2 必须存在,值无所谓,参数param3 必须不存在,只有当请求/testParams.do 而且知足指定的三个参数条件的时候才能访问到该方法。因此当请求/testParams.do?param1=value1¶m2=value2 的时候可以正确访问到该testParams 方法,当请求/testParams.do?param1=value1¶m2=value2¶m3=value3 的时候就不可以正常的访问到该方法,由于在@RequestMapping 的params 参数里面指定了参数param3 是不能存在的。
2.method属性
@RequestMapping (value= "testMethod" , method={RequestMethod. GET , RequestMethod. DELETE })
public String testMethod() {
return "method" ;
}复制代码
在上面的代码中就使用method 参数限制了以GET 或DELETE 方法请求/testMethod.do 的时候才能访问到该Controller 的testMethod 方法。
3.headers属性
@RequestMapping (value= "testHeaders" , headers={ "host=localhost" , "Accept" })
public String testHeaders() {
return "headers" ;
}复制代码
headers 属性的用法和功能与params 属性类似。在上面的代码中当请求/testHeaders.do 的时候只有当请求头包含Accept 信息,且请求的host 为localhost 的时候才能正确的访问到testHeaders 方法。
HttpServlet 对象,主要包括HttpServletRequest 、HttpServletResponse 和HttpSession 对象。 这些参数Spring 在调用处理器方法的时候会自动给它们赋值,因此当在处理器方法中须要使用到这些对象的时候,能够直接在方法上给定一个方法参数的申明,而后在方法体里面直接用就能够了。可是有一点须要注意的是在使用HttpSession 对象的时候,若是此时HttpSession 对象尚未创建起来的话就会有问题。
Spring 本身的WebRequest 对象。 使用该对象能够访问到存放在HttpServletRequest 和HttpSession 中的属性值。
InputStream 、OutputStream 、Reader 和Writer 。 InputStream 和Reader 是针对HttpServletRequest 而言的,能够从里面取数据;OutputStream 和Writer 是针对HttpServletResponse 而言的,能够往里面写数据。
使用@PathVariable 、@RequestParam 、@CookieValue 和@RequestHeader 标记的参数。
使用@ModelAttribute 标记的参数。
java.util.Map 、Spring 封装的Model 和ModelMap 。 这些均可以用来封装模型数据,用来给视图作展现。
实体类。 能够用来接收上传的参数。
Spring 封装的MultipartFile 。 用来接收上传文件的。
Spring 封装的Errors 和BindingResult 对象。 这两个对象参数必须紧接在须要验证的实体对象参数以后,它里面包含了实体对象的验证结果。
一个包含模型和视图的ModelAndView 对象。
一个模型对象,这主要包括Spring 封装好的Model 和ModelMap ,以及java.util.Map ,当没有视图返回的时候视图名称将由RequestToViewNameTranslator 来决定。
一个View 对象。这个时候若是在渲染视图的过程当中模型的话就能够给处理器方法定义一个模型参数,而后在方法体里面往模型中添加值。
一个String 字符串。这每每表明的是一个视图名称。这个时候若是须要在渲染视图的过程当中须要模型的话就能够给处理器方法一个模型参数,而后在方法体里面往模型中添加值就能够了。
返回值是void 。这种状况通常是咱们直接把返回结果写到HttpServletResponse 中了,若是没有写的话,那么Spring 将会利用RequestToViewNameTranslator 来返回一个对应的视图名称。若是视图中须要模型的话,处理方法与返回字符串的状况相同。
若是处理器方法被注解@ResponseBody 标记的话,那么处理器方法的任何返回类型都会经过HttpMessageConverters 转换以后写到HttpServletResponse 中,而不会像上面的那些状况同样当作视图或者模型来处理。
除以上几种状况以外的其余任何返回类型都会被当作模型中的一个属性来处理,而返回的视图仍是由RequestToViewNameTranslator 来决定,添加到模型中的属性名称能够在该方法上用@ModelAttribute(“attributeName”) 来定义,不然将使用返回类型的类名称的首字母小写形式来表示。使用@ModelAttribute 标记的方法会在@RequestMapping 标记的方法执行以前执行。
SpringMVC 支持使用 @ModelAttribute 和 @SessionAttributes 在不一样的模型和控制器之间共享数据。 @ModelAttribute 主要有两种使用方式,一种是标注在方法上,一种是标注在 Controller 方法参数上。
当 @ModelAttribute 标记在方法上的时候,该方法将在处理器方法执行以前执行,而后把返回的对象存放在 session 或模型属性中,属性名称可使用 @ModelAttribute(“attributeName”) 在标记方法的时候指定,若未指定,则使用返回类型的类名称(首字母小写)做为属性名称。关于 @ModelAttribute 标记在方法上时对应的属性是存放在 session 中仍是存放在模型中,咱们来作一个实验,看下面一段代码。
@Controller
@RequestMapping ( "/myTest" )
public class MyController {
@ModelAttribute ( "hello" )
public String getModel() {
System. out .println( "-------------Hello---------" );
return "world" ;
}
@ModelAttribute ( "intValue" )
public int getInteger() {
System. out .println( "-------------intValue---------------" );
return 10;
}
@RequestMapping ( "sayHello" )
public void sayHello( @ModelAttribute ( "hello" ) String hello, @ModelAttribute ( "intValue" ) int num, @ModelAttribute ( "user2" ) User user, Writer writer, HttpSession session) throws IOException {
writer.write( "Hello " + hello + " , Hello " + user.getUsername() + num);
writer.write( "\r" );
Enumeration enume = session.getAttributeNames();
while (enume.hasMoreElements())
writer.write(enume.nextElement() + "\r" );
}
@ModelAttribute ( "user2" )
public User getUser() {
System. out .println( "---------getUser-------------" );
return new User(3, "user2" );
}
}复制代码
当咱们请求 /myTest/sayHello.do 的时候使用 @ModelAttribute 标记的方法会先执行,而后把它们返回的对象存放到模型中。最终访问到 sayHello 方法的时候,使用 @ModelAttribute 标记的方法参数都能被正确的注入值。执行结果以下所示:
Hello world,Hello user210复制代码
由执行结果咱们能够看出来,此时 session 中没有包含任何属性,也就是说上面的那些对象都是存放在模型属性中,而不是存放在 session 属性中。那要如何才能存放在 session 属性中呢?这个时候咱们先引入一个新的概念 @SessionAttributes ,它的用法会在讲完 @ModelAttribute 以后介绍,这里咱们就先拿来用一下。咱们在 MyController 类上加上 @SessionAttributes 属性标记哪些是须要存放到 session 中的。看下面的代码:
@Controller
@RequestMapping ( "/myTest" )
@SessionAttributes (value={ "intValue" , "stringValue" }, types={User. class }) public class MyController {
@ModelAttribute ( "hello" )
public String getModel() {
System. out .println( "-------------Hello---------" );
return "world" ;
}
@ModelAttribute ( "intValue" )
public int getInteger() {
System. out .println( "-------------intValue---------------" );
return 10;
}
@RequestMapping ( "sayHello" )
public void sayHello(Map<String, Object> map, @ModelAttribute ( "hello" ) String hello, @ModelAttribute ( "intValue" ) int num, @ModelAttribute ( "user2" ) User user, Writer writer, HttpServletRequest request) throws IOException {
map.put( "stringValue" , "String" );
writer.write( "Hello " + hello + " , Hello " + user.getUsername() + num);
writer.write( "\r" );
HttpSession session = request.getSession();
Enumeration enume = session.getAttributeNames();
while (enume.hasMoreElements())
writer.write(enume.nextElement() + "\r" );
System. out .println(session);
}
@ModelAttribute ( "user2" )
public User getUser() {
System. out .println( "---------getUser-------------" );
return new User(3, "user2" );
}
}复制代码
在上面代码中咱们指定了属性为 intValue 或 stringValue 或者类型为 User 的都会放到 Session中,利用上面的代码当咱们访问 /myTest/sayHello.do 的时候,结果以下:
Hello world,Hello user210复制代码
仍然没有打印出任何 session 属性,这是怎么回事呢?怎么定义了把模型中属性名为 intValue 的对象和类型为 User 的对象存到 session 中,而实际上没有加进去呢?难道咱们错啦?咱们固然没有错,只是在第一次访问 /myTest/sayHello.do 的时候 @SessionAttributes 定义了须要存放到 session 中的属性,并且这个模型中也有对应的属性,可是这个时候尚未加到 session 中,因此 session 中不会有任何属性,等处理器方法执行完成后 Spring 才会把模型中对应的属性添加到 session 中。因此当请求第二次的时候就会出现以下结果:
Hello world,Hello user210
user2
intValue
stringValue复制代码
当 @ModelAttribute 标记在处理器方法参数上的时候,表示该参数的值将从模型或者 Session 中取对应名称的属性值,该名称能够经过 @ModelAttribute(“attributeName”) 来指定,若未指定,则使用参数类型的类名称(首字母小写)做为属性名称。
到此,SpringMVC的原理以及经常使用注解就介绍的差很少了,平时开发这些就够用了,若是你还想深刻学习SpringMVC知识点,能够关注我我的公众号,里面资源贴有全套的视频教程。
Spring经常使用注解
@AUTOWIRED与@RESOURCE的区别
SpringMVC Controller介绍及经常使用注解
一直以为本身写的不是技术,而是情怀,一篇篇文章是本身这一路走来的痕迹。靠专业技能的成功是最具可复制性的,但愿个人这条路能让你少走弯路,但愿我能帮你抹去知识的蒙尘,但愿我能帮你理清知识的脉络,但愿将来技术之巅上有你也有我。