转眼间又到了三月花开的季节,没一起感觉开篇都在说一些废话,这一期一样不例外。
项目github地址:https://github.com/pc859107393/SpringMvcMybatis
实时项目同步的地址是国内的码云:https://git.oschina.net/859107393/mmBlog-ser
我的简书首页是://www.greatytc.com/users/86b79c50cfb3/latest_articles
上一期是:[手把手教程][第二季]java 后端博客系统文章系统——No6
工具
- IDE为idea16
- JDK环境为1.8
- gradle构建,版本:2.14.1
- Mysql版本为5.5.27
- Tomcat版本为7.0.52
- 流程图绘制(xmind)
- 建模分析软件PowerDesigner16.5
- 数据库工具MySQLWorkBench,版本:6.3.7build
本期目标
- 回顾SpringMvc
- 总结提高
闲聊
最近看了很多技术相关的文档感觉受益良多,我也会不定时的在群里分享一些技术文档,虽然很多都是别人分享给我的。
世上无难事,只要肯攀登。(肯攀登是谁呢?)
虽然说我们是单独做技术的人,但是牛奶和面包总是不能少的。一味画饼充饥的创业都是耍流氓。
最近碰到几个想找我一起创业的人,不签合同,不给现金,都是吹逼项目牛逼,然后想我白干。 可惜我已经过了几句煽情的话就能打动的年纪。
无论谁找你创业,不给真金白银都是耍流氓。
SpringMvc
在前面我们已经做过很多关于SpringMvc的应用,可能大家很多日常的基本操作都有了概念,但是这个操作叫做什么名字呢?怎样更加合理的运用呢?Let's go!
1. 配置SpringMvc
我们需要创建Spring-Web.xml,代表它是我们的Spring依赖的注册文件(context)。
<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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="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.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
上面演示的是我们注册的时候需要的Spring相关的申明。紧接着,我们需要开始一步步的注册web相关的资源:
<!-- 配置SpringMVC -->
<!-- 1.开启SpringMVC注解模式 -->
<!-- 简化配置:
(1)自动注册DefaultAnootationHandlerMapping,AnotationMethodHandlerAdapter
(2)提供一些列:数据绑定,数字和日期的format @NumberFormat, @DateTimeFormat, xml,json默认读写支持
-->
<mvc:annotation-driven/>
当我们引入了上面的代码的时候,我们已经开启了SpringMvc的注解映射(同理我们可以使用非注解映射!)
- 简单的说明下非注解映射就是在Spring-web.xml中直接配置url。
当我们配置了url的访问注解后,按照道理来说只要我们开始配置了,那么我们的地址就是可以开始访问的,但是我们不可能所有的请求都需要框架来处理然后转发吧?so,我们需要配置一些特殊资源的访问路径,比如说静态的js、css、img等等,所以有了如下的配置:
<!--配置静态资源的url映射-->
<mvc:resources mapping="/css/**" location="/static/css/"/>
<mvc:resources mapping="/images/**" location="/static/images/"/>
<mvc:resources mapping="/view/**" location="/static/view/"/>
<mvc:resources mapping="/js/**" location="/static/js/"/>
<mvc:resources mapping="/fonts/**" location="/static/fonts/"/>
<!--配置默认的前端控制器-->
<mvc:default-servlet-handler/>
当然,我们上面得只是简单的配置了一些文件的url目录,同时我们可以部署我们的线上资源的访问权限,如果是非法用户那么就需要把他赶出服务器,所以我们需要请求拦截,并插入一些代码检查之类的操作,所以先配置拦截器如下:
<!-- 访问拦截 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**/**"/>
<bean class="cn.acheng1314.intercepter.LoginHandlerInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
具体的拦截器代码如下:
/**
* Created by mac on 2017/1/27.
*/
public class LoginHandlerInterceptor extends HandlerInterceptorAdapter {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String path = request.getServletPath();
String userId; //登录成功后写入session的用户id
User user; //通过用户ID查询到的用户信息
/*
我们拦截的网址是需要最少是作者权限才能进行编辑的,所以这里我们需要限制访问。
<br/> 同时我们可以看到的是 登录页面必须是所有人都可以访问的,但是如果已经登录成功了,session在有效期内,我们的登录界面就不应该再展示给用户
*/
if (!path.matches(".*/((endSupport)|(commit*)).*")) {
if (path.contains("/main/login")) { //已经登录且身份信息且没有过期,我们直接跳转到后端主页去
try {
userId = request.getSession().getAttribute("userId").toString();
user = userService.findOneById(userId);
if (request.getRequestedSessionId().equals(user.getUserSessionId())) { //前面用户登录后会存入请求的sessionId和当前的sessionId对比
response.sendRedirect(request.getContextPath() + "/endSupport/index");
return false;
}
} catch (Exception e) {
e.printStackTrace();
return true;
}
}
return true;
} else {
try {
userId = request.getSession().getAttribute("userId").toString();
user = userService.findOneById(userId);
if (!request.getRequestedSessionId().equals(user.getUserSessionId())) { //前面用户登录后会存入请求的sessionId和当前的sessionId对比
throw new Exception("用户信息错误!");
}
return true;
} catch (Exception e) {
e.printStackTrace();
response.sendRedirect(request.getContextPath() + "/main/login");
return false;
}
}
}
}
看到上面拦截器相关的代码,大家都会明白,在我的url中只要匹配了endSupport和commit相关字段的,我们都需要用户登录,其实说严格一点按照http请求的几种方式和restful来说没我们需要匹配更多的规则才行。重点思考:我们既然前面看到了拦截器那里仅仅配置了一个拦截器,那么我们能不能配置多个拦截器呢?答案是可以的,interceptors:指定拦截器链,拦截器的执行顺序就是此处添加拦截器的顺序。
通过上面的配置,我们已经可以实现html页面的展示,而且依赖js脚本刷新的动态页面也是基本可以显示了,但是,不是所有的后端工程师都精通web页面的开发,更多时候,可能我们单纯的更喜欢jsp这种方式的页面,所以我们需要添加jsp页面的解析,代码如下:
<!-- 3.配置jsp 显示ViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
配置完成到这里,我们需要的就只剩下文件上传的数据模型和web接口的扫描了。为什么要扫描web接口呢?我们前面开启了注解映射,那么我们避免和项目其他的冲突,我们直接指定web接口的文件存放目录,让程序完成自动扫描,那么我们就只需要关心我们的业务开发了,是不是很爽?
<!--上传文件的处理模型-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10000000"/>
</bean>
<!-- 4.扫描web相关的bean -->
<context:component-scan base-package="cn.acheng1314.controller">
<!-- 制定扫包规则 ,只扫描使用@Controller注解的JAVA类 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
小结
SpringMvc的配置顺序如下:
创建配置文件→加入依赖申明→开启注解式映射(非注解也行)→加入特殊资源访问映射→设置访问拦截→配置动态视图解析→配置web接口扫描规则和文件上传的处理模型。
既然已经配置好了注解开发设置,那么我们需要实际操作体会一下SpringMvc的开发,先看看代码如下:
@Controller
@RequestMapping("/front")
public class FrontWebController {
/**
* 返回主页面
*
* @return
*/
@RequestMapping(value = "/main", method = RequestMethod.GET)
public ModelAndView frontMain(HttpServletRequest request) throws Exception {
ModelAndView view = new ModelAndView("frontMain");
view.addObject("framJson", getFramJson());
view.addObject("postsJson", findPublishPost(request, 1, 10));
return view;
}
在上面得代码中我们是返回一个web页面,这个web页面匹配到的url就是:xxxhost.xxx/front/main。主要规则说明如下:
类用Controller的注解:@Controller,说明这个类是web接口的注册类
RequestMapping的意思是说明这个地址是:“/RequestMapping”
-
http请求的方式有很多,可以在RequestMapping中限制具体的请求方式是什么?
- 具体的形式有:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
- 我们在RequestMapping中发现有更多的说明,如:name、value、path、method、params、headers、consumes、produces,具体的其他用途我们可以详细的查看文档。
ModelAndView主要是用来说明这个请求地址是个web视图。
view.addObject() 是用来将我们希望传输到web页面的数据插入到请求中
在web页面中,我们使用${objName}来获取数据。
通过上面得代码和归纳,我们大概明白了怎么去创建一个web页面需要怎么样的设置,但是我们如果要获取到json数据呢?具体如下:
@RequestMapping(value = "/findPublishPost"
, produces = {APPLICATION_JSON_UTF8_VALUE}
, method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public Object findPublishPost(HttpServletRequest request,
@RequestParam(value = "pageNum", required = false)
Integer pageNum,
@RequestParam(value = "pageSize", required = false)
Integer pageSize) throws Exception {
return "这是返回的json数据";
}
上面得代码我们可以得到json数据,为什么呢?
- 首先我们可以看到我们的RequestMapping多了produces和method。produces是对这个方法的综述,method是这个请求的访问方法,重要的是他们都是数组,也就是可以支持多种形式。
- ResponseBody这个注解是专门用来修饰某个方法,说明这个方法只返回ResponseBody(响应体)。
- RequestParam标记这个请求的参数,默认值是TRUE,除非单独表明required = false
这就完了吗?等等呢,还没完。为啥呢?我们想象一下,当我们要去找人的时候,是不是直接叫xx出来呢?既然这样,我现在需要从一个列表中拿到数据,是不是也应该这样呢?
/**
* RESTful风格的文章页面
*
* @param postId 文章ID
* @return 返回文章页面
*/
@RequestMapping(path = "/post/{postId}", method = RequestMethod.GET)
public ModelAndView getPostView(@PathVariable int postId) {
ModelAndView resultView = new ModelAndView("frontPost");
resultView.addObject("framJson", getFramJson());
resultView.addObject("postJson", getPostById(postId));
return resultView;
}
上线这段代码,我们最终可以看到一个文章详情页,而且重点的是“post/”后面的postId发生了变化,其他的都是对应着变化的。
- 首先在方法上面注解RequestMapping说明请求地址。
- path = "/post/{postId}" 说明url的形式是:/post/xxx。
- @PathVariable int postId说明这里参数postId和上面的{postId}的值相同。
说实话,到了上面这样子,其实大概东西都差不多了,不过需要注意的重点就是:web接口中的参数永远你都是不知道存不存在的,所以使用基本数据类型的参数都是不安全的,所以我们需要使用包装类型。
总结
Spring+SpringMvc类型的框架中,SpringMvc提供了web试图的填充和解析以及http请求的接收、处理和响应,所以我们需要先配置web相关设置,后面才能进行web相关的开发。
最后扯犊子一下,关于中国人、乐天、萨德和韩国我觉得尊重中国人,保护私有财产,韩国棒子滚一边去。