- Spring MVC角色
Spring MVC是一款优秀的控制器框架,我们基于Servlet的思想基础,使用Spring MVC是一件比较简单的事情。只是Spring MVC会实现很多细节化的东西,使得开发的效率很高。Serlvet只是粗浅的处理了HTTP请求,其中并没有牵扯到复杂的需求定制。庆幸的是,Spring MVC实现了很多定制功能并且学习成本不高,而且开发效率大大提高了。
- 处理HTTP请求
如Servlet中存在doGet和doPost方法一样,如果把Spring MVC的方法声明为
method = RequestMethod.GET
则它会处理Get请求,如果使用POST访问,则会返回405的状态号。
HTTP API就是要把接口暴露在URL里,通过URL可以执行你写的函数去处理数据。
在暴露API之前必须定义一个类为Controller,这样框架就会在输入URL的时候定位到这个类的方法。
这些功能的实现原理都是使用Java的Annotation。
@Controller public class controller{ ..... }
普通方法返回的值都会转化成页面URL,比如返回hello,它就会去在WebContent里去寻找相应的资源,现在普遍的框架都是前后分离,所以都使用ResponseBody来构造REST API。
@RequestMapping(value="/invoiceStatus",produces="text/html;charset=UTF-8",method = RequestMethod.GET) public @ResponseBody String getStatus(){ JSONObject jsonResult = new JSONObject(); jsonResult.put(InvoiceStatus.ENTERING.getCode(), InvoiceStatus.ENTERING.getName()); jsonResult.put(InvoiceStatus.CHECKED.getCode(), InvoiceStatus.CHECKED.getName()); jsonResult.put(InvoiceStatus.FINANCE.getCode(), InvoiceStatus.FINANCE.getName()); return resultSuccess("success", jsonResult.toString()); }
如果访问上述方法,则会在页面上显示一串字符串。
在高版本的Spring MVC中支持REST接口控制器
@RestController @Scope("singleton") @RequestMapping(value="/rest") public class restController{ ... }
RestController就是Controller和ResponseBody的组合。
在项目中比如你想获得某些资源,比如用户信息,那么你会定义一个:项目名/user/userInfo,这样的接口,然后把信息封装成JSON字符串返回给前端,然后前端把数据渲染到页面上,这样一个REST API就做好了。
为了更优雅的使用GET请求,Spring MVC支持 "/参数" 这样的形式,替代"?参数名=aaa&参数名=bbb"这样久形式。
@RequestMapping(value="/noNeedLogin/validateUser/{username}",produces="text/html;charset=UTF-8",method = RequestMethod.GET) @ResponseBody public String validateUser(@PathVariable("username")String username){ User u = null; try{ u = uService.validateUser(username); }catch (Exception e) { logger.debug("验证用户出错 : "+e.getMessage()); return exception("验证方法出现异常"); } if(u == null) return failure("不存在此用户"); else return success("用户已存在"); }
如上述实例,将形参映射到URL上,就可以把GET请求变得很“好看”了。
如果要使用Request,Response,Session,则在请求方法中指定形参,方法体内就可使用,框架会对形参进行注入。
public void test(HttpServletRequest request,HttpServletResponse response,HttpSession session){ }
- 事务功能
Spring框架提供了事务管理机制,来控制数据库事务的提交。
事务有ACID特性,在Spring中提供一个注解可以控制事务的隔离性,以及事务的传播特性、超时、只读属性。对于以前写重复的commit来说,框架把重复代码抽取出来,提高了开发效率。
首先在配置文件中添加事务管理器。
在业务类中配置@Service标签,然后再Spring配置文件中写入自动检索Service标签并添加事务。
Spring配置头文件记录,适用于4.0以上的版本:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd"> </beans>
配置事务管理器:
<!--加入过滤的配置是为了使mybatis的事务起作用--> <context:component-scan base-package="Service" /> <!-- 进行主数据库的事务配置,采用默认策略 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> <!-- 配置数据源 使用dbcp连接池--> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="url"> <value>${jdbc.url}</value> </property> <property name="driverClassName" value="${jdbc.driverClass}"/> <property name="username"> <value>${jdbc.username}</value> </property> <property name="password"> <value>${jdbc.password}</value> </property> <!--initialSize: 初始化连接--> <property name="initialSize" value="25"/> <property name="maxTotal" value="20"/> <property name="maxIdle" value="5"/> </bean>
数据源采用了DBCP2连接池。
然后在方法前面适用@Transactional标签
rollbackfor是产生什么样的异常时候回滚,
readOnly是表示只读事务,如果有写操作则会报错,
propagation是事务的传播属性,说明的是事务和事务之间调用的关系,比如一个事务调用另一个事务开不开起一个新事务,
timeout是超时,事务15s内不完成视为超时,
还有一个属性就是事务的隔离级别,isolation,值就是四大隔离级别。
@Transactional(rollbackFor=Exception.class,readOnly = true, propagation = Propagation.REQUIRED,timeout=15) public List<Invoice> getByUserid(long userid){ List<Invoice> results = iDao.findByUserId(userid); return results; }
- 方法级控制-拦截器
通常在前后端架构分离的时候,后台只需要编写数据接口,而数据接口的访问权限,可以采用拦截器来实现。
拦截器主要会过滤url中指定拦截规则的接口,也就是方法级的屏障。
编写拦截器需要实现一个HandlerInterceptor接口,然后配置到mvc的配置文件中。
比如登录拦截器:
package Common.Interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.log4j.Logger; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import Common.CommonInfo; import Model.User; /** * 登录过滤器 * @author ctk * */ public class UserLoginInterceptor implements HandlerInterceptor{ private static Logger logger = Logger.getLogger(UserLoginInterceptor.class); //后置执行 @Override public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { } @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { } //前置执行 @Override public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { logger.debug("用户登录拦截器的前置方法....."); HttpSession session = arg0.getSession(); User u = (User)session.getAttribute(CommonInfo.userInfo); if(u == null) { logger.debug("用户未登录"); arg1.setStatus(401); return false; } else logger.debug("用户已登录:"+u.getUsername()); return true; } }
他会校验session中是否存在用户,不存在用户过滤方法访问就会返回405,return false是不执行所拦截的方法,目前只使用前置拦截,其他两个方法暂时没有研究,配置拦截器的规则如下:
<!--配置拦截器--> <mvc:interceptors> <!--login--> <mvc:interceptor> <mvc:mapping path="/user/**"/> <mvc:mapping path="/invoice/**"/> <mvc:mapping path="/supplier/**"/> <!-- 需排除拦截的地址 --> <mvc:exclude-mapping path="/user/noNeedLogin/**"/> <bean class="Common.Interceptor.UserLoginInterceptor"> </bean> </mvc:interceptor> </mvc:interceptors>
如果需要配置多个拦截器,则顺序的添加到后面即可,只要url符合上述规则就会拦截,除了不需要登录的接口,其他都会拦截。
- MVC跨域配置
跨域配置是前后分离的基础,如果一个网页不在你的项目之下,而是另一个项目启动在Ngnix静态服务器中,它写的请求大多数会试Ajax请求,Ajax请求如果访问了非项目域名,就会有跨域异常,即使解决了浏览器端请求的配置,如果服务端不开放跨域请求的话,前端也拿去不到数据。
MVC在4.0有了很轻松的跨域配置。
<!--配置跨域--> <mvc:cors> <mvc:mapping path="/**" allowed-origins="*" allow-credentials="true" max-age="3000" allowed-methods="GET,POST,OPTIONS"/> </mvc:cors>
对于path下,项目路径下所有方法,允许域名服务器为allowed-origins的跨域请求。
如果需要对接口进行单独跨域,也有一个注解可以实现。
@CrossOrigin(origins = "*", maxAge = 3600)
如果mvc配置文件添加跨域出错,则可能是xsd没有添加到正确的版本。
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
我从4.0改成4.2之后,就通过了。
- 单例模式与原型模式的需求性
如果新建了一个Controller,使用Autowired配置在Spring里面的bean,它正常情况下都是属于单例模式。也就是无论多少次请求,只会生成一个Controller类。博主刚开始的时候很疑惑,单例模式在多用户下是否会有线程安全问题,就本质思考来说,线程安全产生的根本就是对临界区资源的访问,而Spring MVC的设计是,在一个方法内进行业务,并且根据方法的实参不同计算不同的事务,所以不存在单例模式下线程安全问题。如果你的设计需要你把属性写在Controller类下,或者Service类共享某些字段,那样就需要对Bean进行配置,变为原型模式或者是请求模式。就绝大部分需求来说,它们的执行路径就是Controller->Service->Dao->SQL,它们都是根据实参不同而生成不同的SQL,进而展现数据。如果是计算性业务,比如统计登录IP,需要在内存中统计使用到公共数据结构的话,就需要考虑线程安全了。单例模式还有一个很好的优势,就是服务器内存节省,无论多少请求,也不会对堆内存增加压力,因为只会新建一个对象,你能想象就如一个高性能的机器手,在一条流水线上对不同的半成品进行加工的场景么?
配置原型模式和单例模式,只需要一个注解Scope。
@Scope("request")
- 最后,有关web应用
日志是很重要的,对于Log4j的配置,把一切日常的Info打印到一个文件中,然后error打印到另外一个文件,大概就是这样的思路。在实际开发中,大项目情况下,不会像eclipse这样本地运行虚拟机,而是配置一台Linux服务器,然后部署项目,这样就少了直接查看错误的方式了,因为以前都是在eclipse中点击错误调用栈的顶部,看是哪个代码。这时候日志就显得非常重要,日志会把错误信息写到文件中,方便查看追踪错误。其实tomcat提供了一种方便查看控制台的方式,就是logs下有个catalina.out日志,你写了SystemOut它会直接打印出来,这样开发测试就方便了不少。
tomcat每日会做日志分割,实时日志就是没有日期的那个文件。
查询错误或者持续查看使用tail命令
tail -f catalina.out
查询错误使用grep命令,比如
有些莫名的错误需要查看access这个日志,它是记录了http请求响应和状态码的日志。