Starter
依赖包
在加入Spring依赖包的基础上,加入:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
额外包:
<!-- FileUpload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<!-- JSTL,Taglib -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
</dependency>
<!-- Test-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>xmlunit</groupId>
<artifactId>xmlunit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
使用
- 配置web.xml (在配置了Spring的基础上)
<!-- 配置加载DispatchServlet 拦截请求--> <servlet> <servlet-name>xxx</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>xxx</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
配置xxx-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:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <mvc:annotation-driven/> <!-- find controller and registered as beans--> <context:component-scan base-package="com.mytest.controller"/> <!-- PS: 若使用@ControllerAdvice,需如下配置 <context:component-scan base-package="com.mytest.controller" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan> --> <!-- Handle requests for static resources --> <mvc:resources location="/resources/" mapping="/resources/**"/> <!-- use InternalResourceViewResolver to resolve internal view: prefix + logical view name + suffix --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/user/"/> <property name="suffix" value=".jsp"/> </bean> <!-- for file uploads --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="1000000"/> </bean> </beans>
在Spring配置文件(
beans.xml
)中去除加载Controller(避免重复)<context:component-scan base-package="com.mytest" > <context:exclude-filter type="regex" expression="com.mytest.controller.*"/> </context:component-scan>
Controller
@Controller @RequestMapping(value={"/users"}) @SessionAttributes("loginUser") public class UserController { private UserService userService; public UserService getUserService(){return this.userService;} @Inject public void setUserService(UserService userService){this.userService=userService;} // List Users @RequestMapping(value={"/"}) public String list(Model model) { model.addAttribute("users",userService.list()); return "list"; } // get user details by id @RequestMapping(value={"/{id}"},method=RequestMethod.GET) public String details(@PathVariable int id,Model model) { User user=userService.get(id); if(user!=null) model.addAttribute(user); return "create"; } //create user & file upload @RequestMapping(value={"/"},method=RequestMethod.POST) public String create(@Valid User user,BindingResult binding, @RequestParam(value="photos",required=false) MultipartFile[] photos,HttpServletRequest req) throws IOException { if(binding.hasErrors()) return "create"; //file upload String realpath = req.getSession().getServletContext().getRealPath("/resources/upload/"); for(MultipartFile photo : photos) { if(photo.isEmpty()) continue; FileUtils.copyInputStreamToFile(photo.getInputStream(), new File(realpath+"/"+photo.getOriginalFilename())); } //save user userService.save(user) return "redirect:list"; } // login @RequestMapping(value={"/login"},method=RequestMethod.POST) public String login(String username,String password,Model model) { User user=userService.check(username,password); if(user!=null){ model.addAttribute("loginUser",user); //return InternalResourceViewResolver.REDIRECT_URL_PREFIX+"/users" return "redirect:list"; } throw new UserException("Login Fail"); //return "login"; } @ExceptionHandler(value={UserException.class}) public String handlerUserException(Exception ex,HttpServletRequest req) { req.setAttribute("ex",ex.getMessage()); return "error"; } }
JSP
WEB_INF/user/list.jsp
部分代码:<!-- 在页面中引入:--> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!-- 显示登录用户--> <h2>Welcome : ${loginUser.username}<h2> <!-- Menu Link --> <a href="create">Create User</a> | <a href="login">Login User</a> <!-- 循环遍历 model:users--> <h2>User List</h2> <table> <tr> <td>Id</td> <td>userName</td> <td>nickName</td> <td>password</td> <td>email</td> <td>Opera</td> </tr> <c:forEach items="${users}" var="u"> <tr> <td>${u.value.id}</td> <td>${u.value.username }</td> <td>${u.value.nickname }</td> <td>${u.value.password }</td> <td>${u.value.email }</td> <td><a href="${u.key}">Details</a></td> </tr> </c:forEach> </table>
WEB-INF/user/create.jsp
部分代码:<!-- 页面中引入spring form:--> <%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %> <h2>Create User</h2> <sf:form method="post" modelAttribute="user" enctype="multipart/form-data"> UserName:<sf:input path="username"/><sf:errors path="username"/> NickName:<sf:input path="nickname"/><sf:errors path="nickname"/> Password:<sf:password path="password"/><sf:errors path="password"/> Email:</td><td><sf:input path="email"/><sf:errors path="email"/> photo1:<input type="file" name="photos"/> photo2:<input type="file" name="photos"/> <sf:button type="reset">Reset</sf:button> <sf:button type="submit" >Submit</sf:button> </sf:form>
WEB-INF/user/login.jsp
部分代码:<h2>User Login</h2> <form method="post" > UserName:<input name="username" type="text"/> Password:<input type="password" name="password"/> <button type="reset">Reset</button> <button type="submit">Submit</button> </form>
WEB-INF/user/error.jsp
部分代码:<body>${ex}</body>
测试
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration({"classpath:beans.xml","file:src/main/webapp/WEB-INF/mytest-servlet.xml"}) public class MyControllerTest { private MockMvc mockMvc; protected MockHttpSession mockSession; @Inject private WebApplicationContext wac; @Before public void setup() { MockitoAnnotations.initMocks(this); mockMvc =MockMvcBuilders.webAppContextSetup(wac).build(); mockSession=new MockHttpSession(); } @Test public void testList() throws Exception{ MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/users")) .andExpect(MockMvcResultMatchers.view().name("list")) .andExpect(MockMvcResultMatchers.model().attributeExists("users")) .andDo(MockMvcResultHandlers.print()) .andReturn(); printMvcResult(result); Assert.assertNotNull(result.getModelAndView().getModel().get("users")); } private void printMvcResult(MvcResult result) throws IOException { MockHttpServletRequest request= result.getRequest(); MockHttpServletResponse response= result.getResponse(); //ModelAndView mv=getModelAndView() //得到执行后的ModelAndView System.out.println("Request:"); System.out.println("URI:"+request.getRequestURI()); System.out.println("URL:"+request.getRequestURL()); System.out.println("Method:"+request.getMethod()+",ContentType:"+request.getContentType()); //System.out.println("InputStream:"+IOUtils.toString(request.getInputStream())); System.out.println("Response:"); System.out.println("Status:"+response.getStatus()+" ErrorMessage:"+response.getErrorMessage()); System.out.println("content:"+response.getContentAsString()); System.out.println("contentType:"+response.getContentType()); } }
处理流程
Request
会由DispatcherServlet
分配给具体Controller
(由HandlerMapping
根据RequestURL来确定)。Controller
完成处理后,Request
会被发送给一个View
(由ViewResolver
确定)来呈现输出结果
具体过程:
- Request发送,由DispatcherServlet拦截
- 将Request交给HandlerMapping,HandlerMapping根据URL决策使用哪个Controller来处理
- 将Request发送给选定的Controller完成逻辑处理
- 将需要Client显示的信息打包成Model,并标志出处用于渲染输出的视图名称(即逻辑逻辑视图名)发送回DispatcherServlet
- ViewResolver根据逻辑视图名确定真正的视图实现(View)
- 视图将Model渲染输出,通过Response传递回Client
当要为用户展现信息时,SpringMVC可以使用视图解析器选择合适的视图:
View resolver | Description |
---|---|
BeanNameViewResolver | Finds an implementation of View that’s registered as a |
ContentNegotiatingViewResolver | Delegates to one or more other view resolvers, the choice of which is based on the content type being requested. |
FreeMarkerViewResolver | Finds a FreeMarker-based template whose path is determined by prefixing and suffixing the logical view name. |
InternalResourceViewResolver | Finds a view template contained within the web application’s WAR file. The path to the view template is derived by prefixing and suffixing the logical view name. |
JasperReportsViewResolver | Finds a view defined as a Jasper Reports report file whose path is derived by prefixing and suffixing the logical view name. |
ResourceBundleViewResolver | Looks up View implementations from a properties file. |
TilesViewResolver | Looks up a view that is defined as a Tiles template. The name of the template is the same as the logical view name. |
UrlBasedViewResolver | This is the base class for some of the other view resolvers, such as InternalResourceViewResolver. It can be used on its own, but it’s not as powerful as its sub-classes. For example, UrlBasedViewResolver is unable to resolve views based on the current locale. |
VelocityLayoutViewResolver | This is a subclass of VelocityViewResolver that supports page composition via Spring’s VelocityLayoutView (a view implementation that emulates Velocity’s VelocityLayoutServlet). |
VelocityViewResolver | Resolves a Velocity-based view where the path of a Velocity template is derived by prefixing and suffixing the logical view name. |
XmlViewResolver | Finds an implementation of View that’s declared as a |
XsltViewResolver | Resolves an XSLT-based view where the path of the XSLT stylesheet is derived by prefixing and suffixing the logical view name. |
Restful
URI: 统一资源定位符,每种"资源"对应一个特定的URI(无状态); 所以要获取这个资源,访问它的URI就可以,URI为每一个资源的地址或独一无二的识别符
REST=Representational State Transfer(表现层状态转化)
- Representional:客户端和服务器之间,通过HTTP请求的头信息(Accept,Content-Type)指定"资源"的表现形式,例如:xml,json,html
- State Transfer:客户端通过HTTP动词(GET,POST,PUT,PUT,DELETE),对服务器端"资源"进行操作,实现"表现层状态转化"
- transferring the state of resources
REST 中最重要的概念是资源(resources),使用全球 ID(通常使用 URI)标识。 客户端应用程序使用 HTTP 方法(GET/ POST/ PUT/ DELETE)操作资源或资源集。 通常,RESTful Web 服务应该定义以下方面:
- Web 服务的基/根 URI,比如
http://host/<appcontext>/resources
- 支持 MIME 类型的响应数据,包括 JSON/XML/ATOM 等等
- 服务支持的操作集合(例如 POST、GET、PUT 或 DELETE)
Restful
- 面向资源的设计
- 是一种风格,而不是标准
- 其目标是“使延迟和网络交互最小化,同时使组件实现的独立性和扩展性最大化”
- 最主要驱动是分布性和扩展性
Restless & Restful URL示例:
(Restless)
(Restful)
HTTP动词:
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
- DELETE(DELETE):从服务器删除资源。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的
HTTP状态码
Status Codes | Description |
---|---|
200 OK - [GET] | 服务器成功返回用户请求的数据,该操作是幂等的(Idempotent) |
201 CREATED - [POST/PUT/PATCH] | 用户新建或修改数据成功 |
202 Accepted - [*] | 表示一个请求已经进入后台排队(异步任务) |
204 NO CONTENT - [DELETE] | 用户删除数据成功 |
400 INVALID REQUEST - [POST/PUT/PATCH] | 用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的 |
401 Unauthorized - [*] | 表示用户没有权限(令牌、用户名、密码错误) |
403 Forbidden - [*] | 表示用户得到授权(与401错误相对),但是访问是被禁止的 |
404 NOT FOUND - [*] | 用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的 |
406 Not Acceptable - [GET] | 用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式) |
410 Gone -[GET] | 用户请求的资源被永久删除,且不会再得到的 |
422 Unprocesable entity - [POST/PUT/PATCH] | 当创建一个对象时,发生一个验证错误 |
500 INTERNAL SERVER ERROR - [*] | 服务器发生错误,用户将无法判断发出的请求是否成功 |
Restful 设计示例:
- GET /users:列出所有用户
- POST /users:新建一个用户
- GET /users/{id}:获取某个指定用户的信息
- PUT /users/{id}:更新某个指定用户的信息(提供该用户的全部信息)
- PATCH /users/{id}:更新某个指定用户的信息(提供该用户的部分信息)
- DELETE /users/{id}:删除某个用户
- GET /users/{id}/roles:列出某个指定用户的所有角色
- DELETE /users/{id}/roles/{roleId}:删除某个指定用户的指定角色
使用示例
SpringMVC配置加入Restful (
xxx-servlet.xml
)<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="mediaTypes"> <map> <!-- <entry key="html" value="text/html"/> --> <entry key="json" value="application/json"/> </map> </property> <!-- <property name="defaultContentType" value="text/html"/> --> <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/"/> <property name="suffix" value=".jsp"/> </bean> </list> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" > <property name="objectMapper"> <bean class="org.codehaus.jackson.map.ObjectMapper"> <property name="serializationConfig.serializationInclusion"> <value type="org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion">NON_NULL</value> </property> </bean> </property> </bean> </list> </property> </bean>
Controller
@Controller @RequestMapping("/users") public class UserController{ private SysUserService userService; public SysUserService getUserService() {return userService;} @Inject public void setUserService(SysUserService userService) {this.userService = userService;} @RequestMapping(method=RequestMethod.GET) @ResponseBody public Object list(){ List<User> users=this.userService.list(); return users; } @RequestMapping(value={"/{id}"},method=RequestMethod.GET) @ResponseBody public Object getUser(@PathVariable("id") String id){ return this.userService.get(id); } @RequestMapping(method=RequestMethod.POST) @ResponseBody public Object create(@RequestBody User user,HttpServletRequest request){ return this.userService.save(user); } @RequestMapping(method=RequestMethod.POST) @ResponseBody public Object upload(@RequestParam(value="file",required=true) MultipartFile file, @RequestParam(value="remark",required=false) String remark) throws IOException, InvalidFormatException { System.out.println("ContentType:"+file.getContentType()); System.out.println("Name:"+file.getName()); System.out.println("OriginalFilename:"+file.getOriginalFilename()); System.out.println("Size:"+file.getSize()); System.out.println("remark:"+remark); String filename=file.getOriginalFilename(); int pos=filename.lastIndexOf("."); String fileExtesion=(pos!=-1?filename.substring(pos+1):"UnKnow"); String realpath = req.getSession().getServletContext().getRealPath("/resources/uploads/"); FileUtils.copyInputStreamToFile(file.getInputStream(), new File(realpath+File.separatorChar+fileName)); return ResponseUtils.sendSuccess(fileName); } }
测试
//GET MvcResult result=this.mockMvc.perform(MockMvcRequestBuilders.get(url) .session(this.mockSession) .contentType(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultHandlers.print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.success",Matchers.equalTo(true))) .andReturn(); //POST User user=new User(); user.setName("xxx"); user.setAge(xxx); ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(Include.NON_NULL); byte[] reqBytes=mapper.writeValueAsString(user); MvcResult result=this.mockMvc.perform(post(url) .contentType(MediaType.APPLICATION_JSON) .content(reqBytes)) .andDo(MockMvcResultHandlers.print()) .andExpect(status().isOk()) //.andExpect(jsonPath("$.success",Matchers.equalTo(true))) .andExpect(jsonPath("$.name",Matchers.equalTo("xxx"))) .andReturn();
或使用
RestTemplate
RestTemplate restTemplate = new RestTemplate(); User[] users = restTemplate.getForObject(SERVER_URI+"/users", User[].class); User user = restTemplate.getForObject(SERVER_URI+"/users/{id}", User.class,id); User response = restTemplate.postForObject(SERVER_URI+"/users", user, User.class);