为了尽量减少耗时操作对Action执行的影响,使用TaskExecutor线程池来管理耗时任务,作为后台进程执行,从而解决了问题。

场景:
    使用了Struts和Spring,但Struts的Action并未交给Spring容器管理,Spring容器仅仅用来管理Dao。

要求:
    对每个Action,实现向数据库写入Log功能,最好做到不要影响正常的操作流程 。Log的内容是此Action的请求参数,由客户端决定。

分析:
    如何截获或者使得Action执行前后做写入Log动作并非关键,难点在于“不要影响正常的操作流程”。可以将Action交给Spring容器管理,利用AOP功能将向要执行的动作添加在Action执行前后;或者为所有Action添加一含有写入Log动作的父类。
    不影响正常的操作流程,最理想的状态就是实现导弹发射出去后不管的功能。即在Action执行的某个步骤中,执行一下写入Log的命令,然后继续执行后继任务,写入Log任务让其自己去执行,无需等待返回信息。所以写入Log功能就不能与Action处于同一进程,否则Log任务会抢占资源而让Action的后续任务处于等待状态。
    势必需要启动新的进程来执行Log任务。对于一个Action还不算复杂,多个呢?启动的新进程如何管理就成了新的问题。如果解决了这个问题,原来的问题就显得很容易了。

实现:
    根据以上分析,希望有这么一个进程提供者,有这样的功能:能接受任务;当有可用线程时,任务获得调度执行;无可用资源时,任务处于等待状态;任务执行完成之后,释放进程资源给进程管理者。这就是一个进程池的基本功能。正好Spring定义了-- TaskExecutor线程池(想想Spring还真是无所不有啊!),为各种进程池提供了统一的用户接口。
    这里就不去分析各种不同线程池的功能了。简单地讲一下对上面提出要求的解决步骤:
    1 定义写入Log任务;
        线程池可接受的任务都必须是实现了Runable接口的类,因此定义一个写入Log的任务如下:
       
     class  LogTask  implements  Runnable  {
        
        
private AccessLog accessLog = null;
        
private AccessLogDao accessLogDao = null;
        
        
public LogTask(AccessLog accessLog, AccessLogDao accessLogDao) {
            
this.accessLog = accessLog;
            
this.accessLogDao = accessLogDao;
        }

        
        
public void run() {
            accessLogDao.addLog(accessLog);
        }

    }
其中的AccessLog是一个简单Bean,定义了需要写入的各种信息;AccessDao专门向数据库中写入AccessLog中的内容提供服务。在new一个LogTask的时候,必须提供这两个参数。
    2 实现用TaskExcutor执行写入Log任务;
        首先指定TaskExcutor接口的实现者。将具体的TaskExcutor用定义的方式配置并由Spring容器管理:
     < bean  id ="taskExecutor"
        class
="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" >
        
< property  name ="corePoolSize"  value ="2"   />
        
< property  name ="keepAliveSeconds"  value ="200"   />
        
< property  name ="maxPoolSize"  value ="10"   />
        
< property  name ="queueCapacity"  value ="60"   />
    
</ bean >
可以看到,具体使用的是ThreadPoolTaskExecutor。这样更换别的线程池也比较方便。
        将任务加入到进程池并执行则比较简单:
        LogTask logTask  =   new  LogTask(accessLog, accessLogDao);
        taskExecutor.execute(logTask);
加入任务后,进程池会根据调度算法来执行任务(调度算法也可以自定义)。
    3 定义服务接口,并实现;
       定义一个Action可以方便使用的接口:
public   interface  AccessLogService  {

    
public void writeLog(HttpServletRequest request);
}
它接受的参数就是包含了客户端请求参数的 HttpServletRequest。这样就不需要Action去作参数分析了,具体的工作留给 AccessLogService完成。
        实现此接口:
public   class  AccessLogServiceImpl implements AccessLogService  {
    
    
/*
     * (non-Javadoc)
     * @see my.test.service.AccessLogService#writeLog(javax.servlet.http.HttpServletRequest)
     
*/

    
public void writeLog(HttpServletRequest request) {

        
//分析request,创建AccessLog对象
        AccessLog accessLog = new AccessLog();
        
//以下略

        writeLog(accessLog);        
    }

    
    
    
    
//-------------------------------------------------------------------------
    
    
/**
     * 写入Log信息<br>
     * 
     * @param accessLog
     
*/

    
private void writeLog(AccessLog accessLog) {
        
        LogTask logTask 
= new LogTask(accessLog, accessLogDao);
        taskExecutor.execute(logTask);
    }

    
    
/** DAO */
    
private AccessLogDao accessLogDao = (AccessLogDao) getDaoByName("accessLogDao");
    
    
/** 线程池 */
    
private TaskExecutor taskExecutor = (TaskExecutor) getDaoByName("taskExecutor");
    
    
//-------------------------------------------------------------------------
    
    
/**
     * 任务定义
     *
     
*/

    
class LogTask implements Runnable {
        
        
private AccessLog accessLog = null;
        
private AccessLogDao accessLogDao = null;
        
        
public LogTask(AccessLog accessLog, AccessLogDao accessLogDao) {
            
this.accessLog = accessLog;
            
this.accessLogDao = accessLogDao;
        }

        
        
public void run() {
            accessLogDao.addLog(accessLog);
        }

    }

}
这里的任务被作为内部类定义的。

    4 为Action加入写入Log功能。
        直接使用策略模式,为所有Action添加一父类,在父类中调用写入Log功能,而具体的Action执行流程由子类决定。
public   abstract   class  BaseAction  extends  Action  {
    
    
public ActionForward execute(
            ActionMapping mapping,
            ActionForm actionForm, 
            HttpServletRequest request,
            HttpServletResponse response) 
        
throws Exception {

        
try {
            ActionForward forward 
= _execute(mapping, actionForm, request, response);

        }
 catch (Exception e) {
            
throw e;

        }
 finally {
            AccessLogService als 
= new AccessLogServiceImpl();
            als.writeLog(request);
        }

    }


    
//子类必须实现此方法
    public abstract ActionForward _execute( ActionMapping mapping,
            ActionForm actionForm, 
            HttpServletRequest request,
            HttpServletResponse response) 
throws Exception;

}
子类中只需 实现抽象方法 _execute即可。在执行完子类的 _execute之后,才会将写入Log的任务放入进程池,然后返回。具体任务何时被执行,则由进程池管理。
通过分析执行时log可知,Action返回之后,写入Log任务才被执行。
也就是说,这样做了之后,很大程度上减少了对Action执行流程的干扰,同时还完成了比较耗时的写数据库的工作。

总结:这只是为了解决项目中的某个特殊的需求而做的,可能也不是什么好的通用的办法。但它也给我们以后解决比如需要后台执行之类的要求提供了一种可行的方案。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐