极简设计

公司技术架构

svn管理代码 trunk目录下三个文件夹 cpic-web cpic-service cpic-admin 其中 web依赖service,他们是一组 admin是后台,独立

web.xml

trunk\cpic-web\src\main\webapp\WEB-INF\web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>ShenXingTaiBao Web</display-name>
    <!-- Spring ApplicationContext配置文件的路径,可使用通配符,多个路径用,号分隔 此参数用于后面的Spring Context Loader -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:/applicationContext.xml
        </param-value>
    </context-param>

    <!-- Filter 定义 -->
    <!-- Log filter -->
    <filter>
        <filter-name>requestTimeLoggerFilter</filter-name>
        <filter-class>com.ihandy.cpic.web.filter.RequestTimeLoggerFilter</filter-class>
    </filter>
    
    <!-- Character Encoding filter -->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    
    <!-- Cache Control Header filter -->
    <filter>
        <filter-name>cacheControlHeaderFilter</filter-name>
        <filter-class>com.ihandy.cpic.web.filter.CacheControlHeaderFilter</filter-class>
        <init-param>
            <param-name>expiresSeconds</param-name>
            <param-value>-1</param-value>
        </init-param>
    </filter>
    
    <!-- Spring MVC Servlet -->
    <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Spring Hessian Service Exporter -->
    <servlet>
        <servlet-name>hessian</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/hessian-servlet.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <!-- Filter 映射 -->
    <filter-mapping>
        <filter-name>requestTimeLoggerFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- Cache Control Header filter 映射 -->
    <filter-mapping>
        <filter-name>cacheControlHeaderFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- Spring MVC Servlet 映射 -->
    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <!-- Spring Hessian Service Exporter 映射 -->
    <servlet-mapping>
        <servlet-name>hessian</servlet-name>
        <url-pattern>/basicall.do</url-pattern>
    </servlet-mapping>
    
    <!--Spring的ApplicationContext 载入 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Spring 刷新Introspector防止内存泄露 -->
    <listener>
        <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
    </listener>

    <!-- Web 容器启动时,根据模板生成静态页面 -->
    <listener>
        <listener-class>com.ihandy.cpic.web.listener.GenerateHtmlPageListener</listener-class>
    </listener>
    
    <!-- support hessian web context -->
    <listener>
        <listener-class>com.ihandy.cpic.web.listener.HessianWebListener</listener-class>
    </listener>

    <!-- session超时定义,单位为分钟 -->
    <session-config>
        <session-timeout>20</session-timeout>
    </session-config>
    <!-- 出错页面定义 -->
    <error-page>
        <exception-type>java.lang.Throwable</exception-type>
        <location>/common/500.jsp</location>
    </error-page>
    <error-page>
        <error-code>500</error-code>
        <location>/common/500.jsp</location>
    </error-page>
    <error-page>
        <error-code>404</error-code>
        <location>/common/404.jsp</location>
    </error-page>
    <error-page>
        <error-code>403</error-code>
        <location>/common/403.jsp</location>
    </error-page>
    <mime-mapping>
        <extension>manifest</extension>
        <mime-type>text/cache-manifest</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>sql</extension>
        <mime-type>text/plain</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>png</extension>
        <mime-type>image/png</mime-type>
    </mime-mapping>
</web-app>

RequestTimeLoggerFilter

记录请求花费时间

package com.ihandy.cpic.web.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.filter.OncePerRequestFilter;

import com.ihandy.cpic.web.RequestTimeLogger;

public class RequestTimeLoggerFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        RequestTimeLogger.begin(request);

        try {
            filterChain.doFilter(request, response);
        } finally {
            RequestTimeLogger.end();
        }

    }

}
package com.ihandy.cpic.web;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 记录处理每个请求总共花费的时间和在每个方法里花费的时间。
 * 时间格式:最近花费时间/方法开始至结束花费时间/请求总花费时间
 */
@SuppressWarnings("unused")
@Component
@Aspect
public class RequestTimeLogger {
    private static Logger logger = LoggerFactory.getLogger(RequestTimeLogger.class);
    private static String REQUEST_ID = "requestId";
    private static String START = "start";
    private static String LAST = "last";
    private static String NOW = "now";
    private static String REQUEST_URI = "requestUri";
    private static Random r = new Random();

    private static ThreadLocal> contextTL = new ThreadLocal>() {
        @Override
        public Map initialValue() {
            return new HashMap();
        }
    };

    public static void begin(HttpServletRequest request) {
        // 构造请求的唯一标识,用于在日志中追踪请求处理过程
        String requestId = request.getSession().getId() + "_" + new Date().getTime() + "_" + r.nextInt(100000000);
        contextTL.get().put(REQUEST_ID, requestId);

        contextTL.get().put(REQUEST_URI, request.getRequestURI());

        long time = System.currentTimeMillis();
        contextTL.get().put(START, time);
        contextTL.get().put(LAST, time);

        contextTL.get().put(NOW, time);
        log("begin", null);
    }

    public static void end() {
        // 计算花费时间时,使用一致的“现在时间”
        long time = System.currentTimeMillis();
        contextTL.get().put(NOW, time);

        // 方法中花费的时间
        long methodBeginTime = (Long) contextTL.get().get(START);
        long methodElapsed = time - methodBeginTime;

        log("end", methodElapsed);
    }

    private static String getRequestId() {
        return (String) contextTL.get().get(REQUEST_ID);
    }

    private static void log(String message, Long methodElapsed) {
        logger.debug("[" + getRequestId() + "] " + getElapsedTime(methodElapsed) + " " + message + " "
                + contextTL.get().get(REQUEST_URI));
    }

    private static String getElapsedTime(Long methodElapsed) {
        long time = (Long) contextTL.get().get(NOW);
        long total = time - (Long) contextTL.get().get(START);
        long lately = time - (Long) contextTL.get().get(LAST);
        contextTL.get().put(LAST, time);
        return lately + "ms/" + (methodElapsed == null ? "enter/" : methodElapsed + "ms/") + total + "ms";
    }

    @Pointcut("execution(* com.ihandy.cpic.web.**.*Controller.*(..)) || execution(* com.ihandy.cpic.service.*Service.*(..)) || execution(* com.ihandy.cpic.dao.*Dao.*(..))")
    private void controllerServiceDaoMethod() {
    }

    @Before("controllerServiceDaoMethod()")
    public void beforeControllerServiceDao(JoinPoint joinPoint) {
        // 计算花费时间时,使用一致的“现在时间”
        long time = System.currentTimeMillis();
        contextTL.get().put(NOW, time);
        // 用于计算在一个方法中花费了多少时间
        contextTL.get().put(joinPoint.getSignature().toLongString() + " begin", time);
        log(joinPoint.getSignature().toShortString(), null);
    }

    @After("controllerServiceDaoMethod()")
    public void afterControllerServiceDao(JoinPoint joinPoint) {
        // 计算花费时间时,使用一致的“现在时间”
        long time = System.currentTimeMillis();
        contextTL.get().put(NOW, time);
        // 方法中花费的时间
        long methodBeginTime = (Long) contextTL.get().get(joinPoint.getSignature().toLongString() + " begin");
        long methodElapsed = time - methodBeginTime;

        log(joinPoint.getSignature().toShortString(), methodElapsed);
    }
}

log4j配置中 #Project default level log4j.logger.com.ihandy.cpic = DEBUG log4j.logger.com.ihandy.cpic.web.RequestTimeLogger = OFF 默认不启用

CacheControlHeaderFilter


package com.ihandy.cpic.web.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.filter.OncePerRequestFilter;
import org.springside.modules.utils.web.ServletUtils;

/**
 * 为Response设置客户端缓存控制Header的Filter.
 * 
 * eg.在web.xml中设置
 *  <filter>
 *      <filter-name>cacheControlHeaderFilter</filter-name>
 *      <filter-class>org.springside.modules.web.CacheControlHeaderFilter</filter-class>
 *      <init-param>
 *          <param-name>expiresSeconds</param-name>
 *          <param-value>31536000</param-value>
 *      </init-param>
 *  </filter>
 *  <filter-mapping>
 *      <filter-name>cacheControlHeaderFilter</filter-name>
 *      <url-pattern>/img/*</url-pattern>
 *  </filter-mapping>
 * 
 */
public class CacheControlHeaderFilter extends OncePerRequestFilter {

    private long expiresSeconds;

    public CacheControlHeaderFilter() {
        // set default value
        expiresSeconds = ServletUtils.ONE_YEAR_SECONDS;
    }

    /**
     * 设置缓存失效时间,小于等于 0 表示不缓存。缺省值是 1 年。
     */
    public void setExpiresSeconds(long expiresSeconds) {
        this.expiresSeconds = expiresSeconds;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (expiresSeconds <= 0) {
            ServletUtils.setDisableCacheHeader(response);
        } else {
            ServletUtils.setExpiresHeader(response, expiresSeconds);
        }
        filterChain.doFilter(request, response);
    }
}