简介:轻量封装Spring MVC
因为本人在国内最大的电子商务公司工作期间,深感一个好的Web框架可以大大提高工作效率,而一个不好的Web框架,又可以大大的降低开发效率。所以,在根据笔者在从事电子商务开发的这几年中,对各个应用场景而开发的一个轻量封装Spring MVC的一个Web框架。
笔者工作的这几年之中,总结并开发了如下几个框架: summercool-web(Web框架,已经应用于某国内大型网络公司的等重要应用)、summercool-hsf(基于Netty实现的RPC框架,已经应用国内某移动互联网公司)、summercool-ddl(基于Mybaits的分表分库框架,已经应用国内某移动互联网公司);相继缓存方案、和消息系统解决方案也会慢慢开源。Summercool框架做为笔者的第一个开源框架
框架地址:http://summercool.googlecode.com/svn/trunk/summercool-web
应用地址:http://summercool.googlecode.com/svn/trunk/summercool-petstore
工具地址:http://summercool.googlecode.com/svn/trunk/summercool-tools
说明:此框架要用到spring-tools文件夹中的security文件夹中的文件,使用此框架的人员请将security文件夹的内容替换到JDK中的security文件夹中
在介绍summercool框架的几个点的时候,在这里我们再看一下summercool的框架,如下:
一、Pipeline介绍
1. AroundPipeline
package org.summercool.web.servlet.pipeline; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.core.PriorityOrdered; import org.summercool.web.servlet.AroundPipelineChain; /** * * @author:shaochuan.wangsc * @date:2010-3-10 * */ public interface AroundPipeline extends PriorityOrdered { /** * * @author:shaochuan.wangsc * @date:2010-3-10 * @param request * @param response * @param aroundPipelineChain * @throws Exception */ public void handleAroundInternal(HttpServletRequest request, HttpServletResponse response, AroundPipelineChain aroundPipelineChain) throws Exception; }
说明: 1) 通过总的summercool框架图我们可以看出,AroundPipeline相当于Filter的功能,其实笔者也是这么设计的
2) 那有人问为什么这么设计呢,因为之前笔者的summercool是基于servlet的,后来才改成基于filter的;所以这块也就保留了下来,而且笔者也想把整个summercool的框架内部的一些设计接口完整化,所以也没有想去掉。还有一个原因是,因为summercool框架对Response进行了缓冲。
3) AroundPipeline会拦截所有的request请求,所以我们就可以做很多东西在这个层面上,比如说,我们可以做一个web应用的请求监控,监控每秒钟应用请求量,如下:
打开monitor-configurer.xml里面的【RequestMonitor】类的源码:
package org.summercool.platform.web.module.petstore.config.monitor; import java.text.SimpleDateFormat; import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.util.UrlPathHelper; import org.summercool.platform.utils.NumberStatisticUtil; import org.summercool.web.servlet.AroundPipelineChain; import org.summercool.web.servlet.pipeline.AroundPipeline; public class RequestMonitor implements AroundPipeline { private static final String N_CHAR = "/"; private final Logger logger = LoggerFactory.getLogger(RequestMonitor.class); private int order; private UrlPathHelper urlPathHelper = new UrlPathHelper(); private NumberStatisticUtil numberStatisticUtil = new NumberStatisticUtil(); public RequestMonitor() { numberStatisticUtil.setInterval(N_CHAR, 1000L); urlPathHelper.setUrlDecode(false); } public void handleAroundInternal(HttpServletRequest request, HttpServletResponse response, AroundPipelineChain aroundPipelineChain) throws Exception { // if (!logger.isDebugEnabled()) { aroundPipelineChain.handleAroundInternal(request, response, aroundPipelineChain); return; } // long beginTime = System.currentTimeMillis(); // numberStatisticUtil.incrementAndGet(N_CHAR); long threadCount = numberStatisticUtil.getValue(N_CHAR); // try { aroundPipelineChain.handleAroundInternal(request, response, aroundPipelineChain); } finally { if (logger.isInfoEnabled()) { String url = urlPathHelper.getLookupPathForRequest(request); long endTime = System.currentTimeMillis(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String message = "当前Dispatcher[" + request.getRemoteAddr() + "-->" + url + "]" + "当执行的时间为:[" + sdf.format(new Date(beginTime)) + "], " + "当前时间内(秒)执行线程数为:[" + threadCount + "], " + "请求执行的时间为:[" + (endTime - beginTime) + "毫秒]"; logger.info(message); } } } public void setOrder(int order) { this.order = order; } public int getOrder() { return order; } }
A. 启动应用,请求应用页面,会打印出如下信息:INFO o.s.p.w.m.p.c.monitor.RequestMonitor - 当前Dispatcher[127.0.0.1-->/images/tb_pet_2.jpg]当执行的时间为:[2012-03-26 12:08:38], 当前时间内(秒)执行线程数为:[24], 请求执行的时间为:[0毫秒]
B. 上面的信息的含义是:127.0.0.1这个应用,每秒钟的请求量;如上面的一秒种的请求量为24次,最后一次请求的是“/images/tb_pet_2.jpg”资源(因为summercool是基于filter的,所以静态信息的请求也会被拦截)。
C. 其实NumberStatisticUtil类是并发安全的记数工具类,是以"key : value"Map结构的工具类,可以跟据时间设置有效期的时间段。
2. PreProcessPipeline
package org.summercool.web.servlet.pipeline; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.core.PriorityOrdered; import org.springframework.web.servlet.ModelAndView; /** * * @author:shaochuan.wangsc * @date:2010-3-10 * */ public interface PreProcessPipeline extends PriorityOrdered { /** * * @author:shaochuan.wangsc * @date:2010-3-10 * @param request * @param response * @return * @throws Exception */ public boolean isPermitted(HttpServletRequest request, HttpServletResponse response) throws Exception; /** * * @author:shaochuan.wangsc * @date:2010-3-10 * @param request * @param response * @return * @throws Exception */ public ModelAndView handleProcessInternal(HttpServletRequest request, HttpServletResponse response) throws Exception; }
petstore-module.xml
<bean class="org.summercool.web.module.WebModuleUriExtensionConfigurer"> <property name="uriExtensions"> <util:list> <value>.htm</value> </util:list> </property> </bean>
说明:1) 为什么要将上面两段代码都要贴出来的呢?是因为,summercool是基于是filter的,所以会过滤所有的请求并进行处理。但是,有些请求是不需要交给后台处理类处理的,所以需要配置一个规则,要处理哪些请求。
2) "WebModuleUriExtensionConfigurer"就是配置一个请求过滤规则的扩展名辅助类。
3) 流程是: requst --> AroundPipeline --> --[请求url扩展名过滤] --> PreProcessPipeline
如果在处理[请求url扩展名过滤]时,如果扩展名匹配上则会继续交给PreProcessPipeline处理并继续向下执行;如果在处理[请求url扩展名过滤]时扩展规则时没有匹配上,则会直接返回。
应用举例:
1. 比如说,我们现在有这样的一个需求:所有访问summercool-petstore的应用,在未登录的时候只允许访问/index.htm、/login.htm和logout.htm请求页面,而其他的页面则必须只有登录用户才可以该访问,如:/item/{id}.htm
2. 上面的需求已经说的非常清楚了,但是我们在哪里判断用户是否登录才好呢?当然,答案就是用PreProcessPipeline
3. 我们先写一个LoginSecurity.java处理类,代码如下:
package org.summercool.platform.web.module.petstore.config.security; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.summercool.platform.web.module.petstore.config.cookie.CookieUtils; import org.summercool.web.servlet.pipeline.PreProcessPipeline; /** * @Title: LoginSecurity.java * @Package com.gexin.platform.web.module.manager.config.security * @Description: * @author 简道 * @date 2011-11-24 下午1:29:27 * @version V1.0 */ public class LoginSecurity extends AbstractSecurity implements PreProcessPipeline { private int order; // set 方法 public void setOrder(int order) { this.order = order; } public ModelAndView handleProcessInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { return new ModelAndView("redirect:/" + "login.htm"); } public boolean isPermitted(HttpServletRequest request, HttpServletResponse response) throws Exception { if (match(request)) { return true; } else { if (CookieUtils.isLogin(request)) { return true; } else { return false; } } } public int getOrder() { return order; } }
security-configurer.xml
<bean name="loginSecurity" class="org.summercool.platform.web.module.petstore.config.security.LoginSecurity"> <property name="order" value="1" /> <property name="filterPaths"> <util:list> <value>/</value> <value>/index.htm</value> <value>/login.htm</value> <value>/logout.htm</value> <value>/helper.htm</value> </util:list> </property> </bean>
说明:首先,在Spring的配置里面配置一下面PreProcessPipeline的实现类,summercool框架会自动将其扫描到容器中并加载成单例的。
再次,所有的请求都会按PreProcessPipeline的序列进行请求处理,比如说summercool-petstore应用里面只有LoginSecurity权限处理类。
然后,request ---> PreProcessPipeline ( isPermitted() -- [true|false] --> handleProcessInternal() )
如果isPermitted()函数返回为true,则不会执行handleProcessInternal()函数,请求会继续交给下一个PreProcessPipeline或是Controller继续向下面执行
如果isPermitted()函数返回为false,则会执行handleProcessInternal()函数,并返回ModelAndView接口并直接返回,不再继续交给下面的请求处理类,而是直接处理ModelAndView接口直接返回给客户端。
在上面的这个LoginSecurity处理类中,所有的请求都经过isPermitted()函数判断用户是否登录,如果登录则返回true;如果不登录则返回false,然后执行handleProcessInternal()函数,handleProcessInternal()函数将用户重定向到登录页面。
在LoginSecurity处理类中,用户是否登录是通过Cookies进行判断的。而不需要权限过滤的请求url是通过AbstractSecurity类里面的match()函数实现,通过xml配置完成。
3. PostProcessPipeline
package org.summercool.web.servlet.pipeline; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.core.PriorityOrdered; import org.springframework.web.servlet.ModelAndView; /** * * @author:shaochuan.wangsc * @date:2010-3-10 * */ public interface PostProcessPipeline extends PriorityOrdered { /** * * @author:shaochuan.wangsc * @date:2010-3-10 * @param request * @param response * @param modelAndView * @return * @throws Exception */ public boolean isPermitted(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView) throws Exception; public ModelAndView handleProcessInternal(HttpServletRequest request, HttpServletResponse response) throws Exception; }
说明:1) PostProcessPipeline其实只是在isPermitted()函数中,多了一个ModelAndView对象;其余的用法和PreProcessPipeline是一样的。
2) 在Controller请求处理完成之后,在渲染页面之前会执行PostProcessPipeline这个接口的实现类。
3) 为什么要多一个ModelAndView接口呢?因为,我们在这里可以做很多的文章,如这样的一个需求:
我们为了web应用的安全考虑,我们肯定希望在一个请求在处理外部重定向的时候,我们需要肯定一些特定的参数进行重定向到一个页面。如:淘宝应用的在访问“已买到的宝贝”的时候,需要登录才能查看,所以在重定向到登录页面的时候,url后面会带一个redirectURL参数来让用户登录后继续回去上一次访问的页面。那么,这个时候问题来了!
A. redirectURL后面是一个安全的淘宝内部的url地址,那么在应用登录后重定向到goto指定的地址是没有问题的。
B. 如果要是被黑客给黑了,redirectURL的地址是一个不安全的地址,那么要怎么处理呢?
C. 我们是否有一个统一的办法来处理这样的情况呢?
D. 当然是PostProcessPipeline了;所有的Controller执行完成后,在跳转到页面之前都会执行PostProcessPipeline,所以我们可以通过ModelAndView参数来查看是否View中的地址是否在我们应用允许跳转的白名单之中,如果允许则通过;如果不允许则做另一处理。(在summercool-petstore应用中,笔者没有给出这个接口的实现)
4. ExceptionPipeline
package org.summercool.web.servlet.pipeline; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.core.PriorityOrdered; import org.springframework.web.servlet.ModelAndView; /** * * @author:shaochuan.wangsc * @date:2010-3-10 * */ public interface ExceptionPipeline extends PriorityOrdered { /** * * @author:shaochuan.wangsc * @date:2010-3-10 * @param request * @param response * @param modelAndView * @param throwable * @throws Exception */ public void handleExceptionInternal(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView, Throwable throwable) throws Exception; }
说明:Spring MVC在处理Controller抛出的错误信息的时候可以拦截,交给ExceptionResolver处理,但是如果vm、jsp和ftl这样的页面出错的时候,Spring MVC的ExceptionResolver是无法处理的;直接抛出给Response并且示给客户端。
Spring MVC只能定制在异常信息类型进行统一错误信息进行处理,如: UserException --> /user/error
而我们经常会遇到跟据UserException中的某一属性,如errorCode属性的不同值来渲染不出的页面。
如果我们有上面的需求,那么我们怎么处理呢?那么我们就要用ExceptionPipeline了,因为该类会拦截所有的异常信息,并且交给handExceptionInternal()函数处理;
expception-configurer.xml里面有一个默认的ExceptionPipeline实现,如下:
package org.summercool.platform.web.module.petstore.config.exception; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.ModelAndView; import org.summercool.util.StackTraceUtil; import org.summercool.web.servlet.pipeline.ExceptionPipeline; public class DefaultExceptionHandler implements ExceptionPipeline { private Logger logger = LoggerFactory.getLogger(getClass()); private int order; public void handleExceptionInternal(HttpServletRequest request, HttpServletResponse response, ModelAndView mv, Throwable throwable) throws Exception { // 打印错误信息 String stackTrace = StackTraceUtil.getStackTrace(throwable); // 记录错误信息 logger.error(stackTrace); if (mv != null) { mv.setViewName("redirect:/index.htm"); } else { throwable.printStackTrace(); } } public void setOrder(int order) { this.order = order; } public int getOrder() { return order; } }
说明:上面的代码的意思是,如果发生异常;handleExceptionInternal()函数会先获取异常信息并打印到日志中,然后通过ModelAndView对象来跟据业务的需要来自定义要跳转的页面。
上面的代码就是如果发生任何的异常,则跳转到/index.htm页面
二、summercool框架对Cookies封装
1. cookie-configurer.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:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <bean name="cookieConfigurer" class="org.summercool.web.module.CookieModuleConfigurer"> <property name="cryptoKey" value="^#16qweqv8cde729!@#$3450abfg^%" /> <property name="cookieConfigurerList"> <util:list> <bean name="id" class="org.summercool.web.beans.cookie.CookieConfigurer"> <property name="domain" value="${org.summercool.petstore.domain}" /> <property name="lifeTime" value="-1" /> <property name="name" value="id" /> <property name="clientName" value="__i__" /> <property name="encrypted" value="true" /> </bean> <bean name="uname" class="org.summercool.web.beans.cookie.CookieConfigurer"> <property name="domain" value="${org.summercool.petstore.domain}" /> <property name="lifeTime" value="-1" /> <property name="name" value="uname" /> <property name="clientName" value="__uli__" /> <property name="encrypted" value="true" /> </bean> <bean name="password" class="org.summercool.web.beans.cookie.CookieConfigurer"> <property name="domain" value="${org.summercool.petstore.domain}" /> <property name="lifeTime" value="-1" /> <property name="name" value="password" /> <property name="clientName" value="__up__" /> <property name="encrypted" value="true" /> </bean> <bean name="csrf" class="org.summercool.web.beans.cookie.CookieConfigurer"> <property name="domain" value="${org.summercool.petstore.domain}" /> <property name="lifeTime" value="-1" /> <property name="name" value="csrf" /> <property name="clientName" value="__rf__" /> <property name="encrypted" value="true" /> </bean> </util:list> </property> </bean> </beans>
在上面这个配置文件中,最重要的是CookieConfigurer类配置,如下:
<bean name="id" class="org.summercool.web.beans.cookie.CookieConfigurer"> <property name="domain" value="${org.summercool.petstore.domain}" /> <property name="lifeTime" value="-1" /> <property name="name" value="id" /> <property name="clientName" value="__i__" /> <property name="encrypted" value="true" /> </bean>
说明:1) domain: cookie存放在的域名(笔者建议设置为一级域名,这样二级域名的应用也可以获取和共享一级域名的cookie)
2) lifeTime:设置cookie的有效期
3) name:服务端的cookie名称,因为开发者在使用时,要知道设置了哪个cookie
4) clientName:相对于服务端的cookie名称,这个是客户端的cookie名称。
比如说吧,我们设置了一个cookie的值,是不想让客户端的用户可以通过工具查看到我们设置的cookie名称能猜出我们的含义的,那么我们就要设置clientName,让用户端看到的是不知道何意义的cookie名称,而在服务端,开发人员又可以明确知道cookie的名称name。
5) encrypted:是否进行加密处理;设置了此属性,CookieConfigurer类会自动对该cookie进行加密和解密。
<property name="cryptoKey" value="^#16qweqv8cde729!@#$3450abfg^%" />
xml中上面这段的配置就是加密混淆串,笔者建议不用的开发者如何使用summercool框架的时候,请更改此加密混淆串。
应用举例:CookiesUtils.java
public static void writeCookie(HttpServletRequest request, UserDO userDO) { if (request == null || userDO == null) { throw new IllegalArgumentException(); } CookieModule jar = (CookieModule) request.getAttribute(CookieModule.COOKIE); if (jar == null) { throw new NullPointerException(); } jar.remove(CookieConstants.MANAGER_ID_COOKIE); jar.remove(CookieConstants.MANAGER_PWD_COOKIE); jar.remove(CookieConstants.MANAGER_UNAME_COOKIE); jar.set(CookieConstants.MANAGER_ID_COOKIE, userDO.getId().toString()); try { jar.set(CookieConstants.MANAGER_UNAME_COOKIE, URLEncoder.encode(userDO.getUserName(), "UTF-8")); } catch (UnsupportedEncodingException e) { } jar.set(CookieConstants.MANAGER_PWD_COOKIE, userDO.getPassword()); } public static void clearCookie(HttpServletRequest request) { CookieModule jar = (CookieModule) request.getAttribute(CookieModule.COOKIE); if (jar != null) { jar.remove(CookieConstants.MANAGER_ID_COOKIE); jar.remove(CookieConstants.MANAGER_UNAME_COOKIE); jar.remove(CookieConstants.MANAGER_PWD_COOKIE); } } public static Long getUserId(HttpServletRequest request) { if (request == null) { throw new IllegalArgumentException(); } CookieModule jar = (CookieModule) request.getAttribute(CookieModule.COOKIE); if (jar == null) { return null; } String idStr = jar.get(CookieConstants.MANAGER_ID_COOKIE); if (StringUtils.isNumeric(idStr)) { return Long.valueOf(idStr); } return null; }
说明:summercool框架已经对cookies进行了封装,在配置文件配置完成后就可以直接使用了
只要通过reqeust对象就可以直接设置和获取cookie的相关信息了。
笔者认为,所有的用户登录信息最好不用要session实现,因为session还要解决分布式的问题。笔者建议登录信息全部都存放在cookie里面是非常好的,因这样不仅可以实现登录信息的保存而且大大降低了开发难度;相关于变相的实现了无状态登录。
三、summercool框架对UrlRewrite封装
1. 比如说我们有这样的一个需求,所有的页面都要通过/item/1.htm这样的url地址来访问“产品的详细页面”
2. 像上面这样的配置规则,我不需要多于的代码编写,可以自动生成上面这样风格的url或是自定义的url风格
3. 上面成生的url风格中,我们还可以提取“1”这样的参数或是url中的参数可以直接提取出来
请看url-configurer.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:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/item/*.htm" value="/item/item_detail.htm" /> </map> </property> </bean> <bean name="urlModuleConfigurer" class="org.summercool.web.module.UrlBuilderModuleConfigurer"> <property name="urlBuilderBeanMap"> <util:map> <entry> <key><value>item</value></key> <bean name="id" class="org.summercool.beans.url.DefaultUrlBuilderBeanDefinition"> <property name="seq" value="1" /> <property name="uriTemplate" value="/item/{id}.htm" /> </bean> </entry> </util:map> </property> </bean> </beans>
说明:1) SimpleUrlHandlerMapping:可以配置/item/*.htm这样的url地址全部都交给/item/item_detail.htm地址对应的Controller(/item/ItemDetailController.java)来处理。
2) UrlBuilderModuleConfigurer:可以配置以模块为单位的url地址映射规则,并加入序号,如:
<entry> <key><value>item</value></key> <bean name="id" class="org.summercool.beans.url.DefaultUrlBuilderBeanDefinition"> <property name="seq" value="1" /> <property name="uriTemplate" value="/item/{id}.htm" /> </bean> </entry>
在上面这段配置中,我们可以配置一个item模块的url地址规则为:/item/{id}.htm
在freemarker页面中,如果想使用上面规则的地址,则使用url内置函数:${url("item",param("id","1"))}
在上面这个函数中,item:代表模块名,param是freemarker的内置函数,返回为map类型的数据,key=id, value=1
如果大家细点一点,会发现上面有一个人seq的一个参数,这个是干什么用的呢?因为有时候,一个item模块可能会有很多的模版规则,如/item/c{category}.htm
上面这个模版的意思是,查到一个item某类目下面的详细信息,这样的话我们可以将seq设置成2
页面上可以做这样的使用:${url("item",param("category","1"),"2")} --> 其中的"2"就是seq的值,是让url函数调用url模版的时候,只调用seq=2的那个模版。当然,也可以不写(默认会调用第一个配置的url模版,即seq=1的模版)。
所以,url地址规则:${url("item",param("id","1"))} --> /item/1.htm;具体的应用请查看summercool-petstore应用的/index.ftl页面,如下:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="utf-8"> <title>Summercool, Petstore</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content=""> <meta name="author" content=""> <!-- Le styles --> <link href="/css/bootstrap.css" rel="stylesheet"> <style type="text/css"> body { padding-top: 60px; padding-bottom: 40px; } #tb td ,#tb th{ border-top: 0px; } </style> <link href="/css/bootstrap-responsive.css" rel="stylesheet"> <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements --> <!--[if lt IE 9]> <script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> </head> <body> ${widget("/petstore/widgets/header")} <div class="container"> <table id="tb" class="table table-striped"> <tbody> <tr> <td class="span1"><img src="/images/tb_pet_1.jpg"/></td> <td class="span12"><a href="${url("item",param("id","1"))}">兔子1</a></td> <td>说明</td> </tr> <tr> <td class="span1"><img src="/images/tb_pet_2.jpg"/></td> <td class="span12">兔子2</td> <td>说明</td> </tr> <tr> <td class="span1"><img src="/images/tb_pet_3.jpg"/></td> <td class="span12">小狗</td> <td>说明</td> </tr> </tbody> </table> <hr> ${widget("/petstore/widgets/footer")} </div> </body> </html>
3) 提取url中的参数,上面的例子只是提到了规则url地址的生成,但是如何将这些url中的参数提取出来呢?那么我们就要看一下,我们url对应处理的Controller,/item/ItemDetailController.java,如下:
package org.summercool.platform.web.module.petstore.controllers.item; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; import org.summercool.beans.url.UrlBuilderBeanDefinition; import org.summercool.web.module.url.UrlBuilderModule; public class ItemDetailController extends AbstractController { @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { UrlBuilderModule urlBuilderModule = (UrlBuilderModule) request.getAttribute(UrlBuilderModule.URL_BUILDER); UrlBuilderBeanDefinition urlBuilderBean = urlBuilderModule.matchUrlBuilderBean(); // 如果在访问detail页面时,没有找查到相对应的url规则直接返回到/login.htm页面 if (urlBuilderBean == null || !urlBuilderBean.isMatched()) { return new ModelAndView("redirect:/index.htm"); } // Map<String, String> map = urlBuilderBean.getUriTemplateVariables(); try { String id = map.get("id"); // 在这里可以写一些业务逻辑,比如在DB中查找到item信息收直接显示detail页面,否则返回到/login.htm页面 Long.valueOf(id); } catch (Exception e) { return new ModelAndView("redirect:/index.htm"); } // return new ModelAndView("/petstore/views/item/itemDetail", map); } }
说明:上面的代码中,有两行代码比较关键,如下:
UrlBuilderModule urlBuilderModule = (UrlBuilderModule) request.getAttribute(UrlBuilderModule.URL_BUILDER); UrlBuilderBeanDefinition urlBuilderBean = urlBuilderModule.matchUrlBuilderBean();
这段代码中的两行,是让开发人员可以通过上面的方式来获得url模版所匹配的地址,如果匹配到则可以提取出url模版中所对应的参数,如:
if (urlBuilderBean == null || !urlBuilderBean.isMatched()) { return new ModelAndView("redirect:/index.htm"); }
上面的代码是,如果匹配不到url对应的模版或是匹配不到其中的具体的规则,则重定向到/index.htm页面。
Map<String, String> map = urlBuilderBean.getUriTemplateVariables(); try { String id = map.get("id"); // 在这里可以写一些业务逻辑,比如在DB中查找到item信息收直接显示detail页面,否则返回到/login.htm页面 Long.valueOf(id); } catch (Exception e) { return new ModelAndView("redirect:/index.htm"); }
上面的代码中,如果匹配到的url模版则可以通过上面的方式直接获取模版中url的参数map。
上面的代码中加入了判断map参数的合法行(笔者也只是示意一下而已)。
补充:到这为止,差不多summercool的框架的主要特性就差不多全都介绍完了。因为笔者的工作调动原因,没有太多的时间来写文档,所以只能写这些了,下一篇会简单介绍一下summercool框架其他的一些小特性。
相关推荐
笔者工作的这几年之中,总结并开发了如下几个框架: summercool(Web 框架,已经应用于某国内大型网络公司的等重要应用)、summercool-hsf(基于Netty实现的RPC框架,已经应用国内某移动互联网公司)、 summercool-...
summercool-hsf Automatically exported from code.google.com/p/summercool-hsf 1.目前为止性能最高的RPC远程通讯框架 2....笔者还开发了web层的框架,也在国内最大的电子商务公司使用,平稳运行两年时间: 地址:
笔者工作的这几年之中,总结并开发了如下几个框架: summercool( Web框架,已经应用于某国内大型网络公司的等重要应用)、summercool-hsf(基于Netty实现的RPC框架,已经应用国内某移动互联网公司)、summercool-...
SSO单点登录概要设计说明书.doc
奥塔北煤矿6.0 Mta新井设计说明书.docx
内容概要:本文详细介绍了基于S7-200 PLC和组态王软件构建喷泉控制系统的全过程。首先明确了系统的IO分配,包括启动按钮、停止按钮以及喷泉水泵的连接方式。接着展示了梯形图程序的设计,涵盖了基本的启停控制逻辑、定时循环和模式切换机制。随后提供了详细的接线图原理图,解释了输入输出部分的具体接线方法。最后讲述了组态王的画面设计,包括创建工程、定义变量和绘制监控界面等步骤。此外还分享了一些调试过程中遇到的问题及解决方案。 适合人群:对自动化控制感兴趣的初学者和技术人员,尤其是那些希望深入了解PLC编程和人机界面设计的人群。 使用场景及目标:适用于小型喷泉项目的实际控制系统开发,旨在帮助读者掌握PLC编程技巧、熟悉组态软件的应用,并能够独立完成类似的自动化控制系统设计。 其他说明:文中不仅包含了理论知识讲解,还附带了许多实践经验分享,如硬件配置建议、常见错误规避措施等,有助于提高实际操作能力。
计算机二级PPT精选二十套(标红)
保险公司IT变更管理流程设计说明书.doc.doc
毕业设计说明书A江坝后式厂房双曲拱坝设计.pdf
内容概要:文档《计算机二级MS精选300道选择题.docx》涵盖了计算机二级考试中Microsoft Office软件(Word、Excel、PowerPoint)及计算机基础知识的选择题。题目涉及软件操作技巧、功能应用、常见问题解决等方面,旨在帮助考生熟悉并掌握相关知识点,提高应试能力。文档内容详尽,涵盖面广,从基础操作到高级应用均有涉及,适合考生全面复习备考。 适用人群:准备参加计算机二级考试的学生及相关从业人员,特别是需要强化Office软件操作技能和计算机基础知识的人员。 使用场景及目标:①考生可以在复习过程中使用这些选择题进行自我检测,巩固所学知识;②教师可以将其作为教学辅助材料,帮助学生更好地理解和掌握课程内容;③培训机构可以用这些题目作为测试题库,评估学员的学习效果。 其他说明:文档不仅提供了大量的选择题,还详细解析了每道题目的答案,有助于考生深入理解知识点。此外,题目内容紧跟最新考试大纲,确保考生能够获得最有效的备考资料。
内容概要:本文介绍了一种创新的方法,利用多目标黏菌优化算法(MOSMA)来优化支持向量机(SVM)的参数C和gamma,从而提高回归预测的效果。首先详细解释了MOSMA的工作原理,包括黏菌权重更新、快速非支配排序以及自适应参数调整等关键技术点。接着展示了具体的Python代码实现,涵盖数据预处理、适应度函数定义、参数更新规则等方面。实验结果显示,在风电功率预测等多个应用场景中,相较于传统的网格搜索方法,MOSMA能够更快更有效地找到最优参数组合,显著提升了预测性能。 适合人群:从事机器学习研究或应用开发的技术人员,尤其是关注SVM参数优化及回归预测领域的从业者。 使用场景及目标:适用于需要进行高效参数寻优的回归预测任务,如风电功率预测、设备负载预测等。主要目标是通过改进SVM参数配置,获得更高的预测精度和更好的泛化能力。 其他说明:文中提供了完整的代码示例和详细的实施步骤指导,帮助读者快速理解和应用这一先进的优化技术。此外,还讨论了一些常见的注意事项和技术细节,如数据标准化、参数范围设定、并行化改造等。
毕业设计 某油库设计说明书.pdf
Q235钢板焊接工艺设计说明书.docx
75t循环流化床锅炉设计说明书.doc
(最新修订版)直列四缸柴油机配气机构设计毕业论文设计说明书.doc
内容概要:《deepseek大模型生态报告 2025年2月》详细介绍了DeepSeek大模型的背景、应用现状、技术特点及其产业生态。DeepSeek由杭州深度求索公司创立,通过一系列技术创新,如多层注意力架构(MLA)、FP8混合精度训练框架、DualPipe跨节点通信等,显著降低了训练成本和提高了模型性能。DeepSeek在国内和国际市场迅速崛起,登顶苹果应用商店免费APP下载排行榜,并被多家企业和机构接入,包括华为、三大运营商、微软、英伟达等。其开源策略和低成本优势对全球科技供应链和资本市场产生了深远影响,尤其是在AI领域。 适合人群:对人工智能、大模型技术感兴趣的科技爱好者、研究人员、企业家及政策制定者。 使用场景及目标:①了解DeepSeek大模型的技术创新和应用现状;②探讨DeepSeek对全球AI产业生态的影响;③分析DeepSeek在不同行业(如金融、医疗、教育、制造等)的具体应用案例。 其他说明:报告还涵盖了各国政府及相关机构对DeepSeek的态度和政策回应,以及DeepSeek对未来AI技术发展和国际竞争格局的启示。此外,报告深入剖析了DeepSeek在技术架构、数据策略和工程实践方面的突破,揭示了其成功背后的组织文化和创新机制。
内容概要:本文详细介绍了利用粒子群算法解决电动汽车区域综合能源系统中光伏电站、充电桩运营商和电网公司在定价上的三方博弈问题。通过MATLAB代码实现了粒子群算法的具体应用,包括参数设置、适应度函数设计、粒子更新策略以及结果可视化。文中展示了如何将三方定价变量编码成多维粒子,并通过目标函数计算和约束处理确保粒子在合理的解空间内搜索最优解。最终得到的电价曲线反映了不同时间段内的供需变化,验证了算法的有效性和实用性。 适合人群:从事能源系统优化、智能算法应用的研究人员和技术开发者。 使用场景及目标:适用于需要进行能源系统优化调度的场景,特别是涉及多方利益协调的问题。目标是找到光伏电价、充电桩电价和电网电价的最佳组合,使得三方利益达到最优平衡。 其他说明:建议在调试过程中关注特定时段的电价突变,适当调整参数如社会认知系数和社会学习因子,以获得更好的收敛效果。此外,初始粒子的位置选择对收敛速度有很大影响,推荐采用高斯扰动等方法进行初始化。
WY02锥齿轮的加工工艺规程及工时定额计算 课程设计说明书.docx
项目管理制度范文.docx
内容概要:本文深入探讨了双馈风力发电机(DFIG)的仿真建模及其关键技术模块。首先介绍了最大功率跟踪(MPPT)模块的工作原理,利用爬山算法优化风能利用率。接着详细讲解了转子侧变流器的矢量控制,包括坐标变换、PI调节器参数设定以及抗饱和处理。文中还讨论了网侧变流器的直流电压控制方法,强调了双闭环结构的重要性,并分享了低电压穿越仿真的实践经验。此外,文章详细解释了功率解耦控制和变速恒频实现的技术细节,提供了丰富的故障案例和调试技巧。 适合人群:从事风电工程研究和技术开发的专业人士,尤其是对DFIG仿真建模感兴趣的工程师和研究人员。 使用场景及目标:适用于希望深入了解DFIG内部机制的研究人员,帮助他们掌握从基本原理到复杂控制策略的设计与实现。同时,也为实际工程项目提供宝贵的调试经验和故障排除指南。 其他说明:文章不仅涵盖了理论分析,还包括大量实用的代码片段和具体的参数配置建议,确保读者能够顺利构建并运行仿真模型。