首页 > 代码库 > Spring MVC项目功能不完全指北

Spring MVC项目功能不完全指北

  • 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="http://www.mamicode.com/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="http://www.mamicode.com/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请求响应和状态码的日志。

技术分享

 

Spring MVC项目功能不完全指北