如何从 Quartz 作业中执行 Struts 2 Action。如何获得容器?

Posted

技术标签:

【中文标题】如何从 Quartz 作业中执行 Struts 2 Action。如何获得容器?【英文标题】:How to execute a Struts 2 Action from inside a Quartz job. How to obtain the Container? 【发布时间】:2012-06-06 07:08:33 【问题描述】:

我正在尝试从 Quartz 作业内部执行 Struts2 动作 - 概括,从不是 HTTP 请求处理的任何上下文。

我从 http://struts.apache.org/2.0.6/docs/how-can-we-schedule-quartz-jobs.html 开始,但该文档似乎已经过时了。

我相信(但我可能错了)我把它归结为需要获取一个 Container 对象:

import java.util.HashMap;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.DefaultActionProxyFactory;

...

HashMap ctx = new HashMap();
DefaultActionProxyFactory factory= new DefaultActionProxyFactory();
factory.setContainer(HOW DO I GET THE CONTAINER??);
ActionProxy proxy = factory.createActionProxy("", "scheduled/myjob", ctx);

一种解决方案是针对 localhost 发出一个 http 请求(通过 TCP)。我宁愿避免这种情况。

【问题讨论】:

您为什么要这样做? S2具体为web层;任意进程可调用的功能应该在服务中隔离。您需要复制哪些 S2 功能?注射?使用弹簧。拦截器? 我需要渲染一个 FreeMarker 模板来生成电子邮件的 HTML。我可以做到这一点——使用我继承的一些代码,我只理解了一半——在一个动作中,但它在其他地方不起作用。 最近我在 Quartz 上做了很多工作,我看不出您的要求和 S2 之间有任何关系。Quartz 主要用于触发某种工作,它们与您使用的容器无关因为与 Quartz 更相关的是您的服务层。奇怪的是,您为什么要从那里执行 Action?? 我之前回答中提到的邮件需要根据定时作业的结果发送。所以我需要做这样的事情:***.com/questions/3985373/…——除了由 Quartz 而不是由 HTTP 请求触发的。 @jsalvata:发送邮件与S2无关。 【参考方案1】:

我有点担心提供这个答案可能会鼓励某些人这样做,但作为概念证明并实际为任何可能出于任何原因可能的人提供解决方案(也许他们正在继承一些被破坏的应用程序,这是需要?),需要在正常请求上下文之外执行 Struts2 操作。

但是,这是一个原始的(作为起点提供,而不是最佳实现)但有效的解决方案:

首先,将这三个类添加到一个名为 com.***.struts2.quartz 的包中:

一个简单的作业,它只要求给定作业上下文的代理并执行它:

package com.***.struts2.quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class ActionJob implements Job 

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException 

        try 
            QuartzActionProxyFactory.getActionProxy(context).execute();
         catch (Exception e) 
            e.printStackTrace();
            throw new JobExecutionException(e);
        

    


一些用于传递动作细节的常量:

package com.***.struts2.quartz;

public class QuartzActionConstants 

    public static final String NAMESPACE = "struts.action.namespace";
    public static final String NAME = "struts.action.name";
    public static final String METHOD = "struts.action.method";


可以从 ActionJob 静态访问的自定义 ActionProxyFactory:

package com.***.struts2.quartz;

import java.util.HashMap;
import java.util.Map;

import org.apache.struts2.impl.StrutsActionProxyFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;

public class QuartzActionProxyFactory extends StrutsActionProxyFactory 

    private static ActionProxyFactory actionProxyFactory;

    public QuartzActionProxyFactory() 
        actionProxyFactory = this;
    

    public static ActionProxy getActionProxy(JobExecutionContext context) throws JobExecutionException 

        ActionProxy actionProxy = null;

        try 
            @SuppressWarnings("unchecked")
            Map<String, Object> actionParams = context.getJobDetail().getJobDataMap();
            Map<String, Object> actionContext = new HashMap<String, Object>();
            actionContext.put(ActionContext.PARAMETERS, actionParams);

            actionProxy = actionProxyFactory.createActionProxy(
                    (String) actionParams.get(QuartzActionConstants.NAMESPACE),
                    (String) actionParams.get(QuartzActionConstants.NAME), 
                    (String) actionParams.get(QuartzActionConstants.METHOD), 
                    actionContext, 
                    false, //set to false to prevent execution of result, set to true if this is desired 
                    false);

         catch (Exception e) 
            throw new JobExecutionException(e);
        

        return actionProxy;
    


然后,在你的 struts.xml 中,添加:

<bean name="quartz" type="com.opensymphony.xwork2.ActionProxyFactory" class="com.***.struts2.quartz.QuartzActionProxyFactory"/>
<constant name="struts.actionProxyFactory" value="quartz"/>

然后您可以使用一些简单的代码来安排动作执行:

SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
scheduler.start();
JobDetail jobDetail = new JobDetail("someActionJob", Scheduler.DEFAULT_GROUP, ActionJob.class);

@SuppressWarnings("unchecked")
Map<String, Object> jobContext = jobDetail.getJobDataMap();
jobContext.put(QuartzActionConstants.NAMESPACE, "/the/action/namespace");
jobContext.put(QuartzActionConstants.NAME, "theActionName");
jobContext.put(QuartzActionConstants.METHOD, "theActionMethod");

Trigger trigger = new SimpleTrigger("actionJobTrigger", Scheduler.DEFAULT_GROUP, new Date(), null, SimpleTrigger.REPEAT_INDEFINITELY, 1000L);
scheduler.deleteJob("someActionJob", Scheduler.DEFAULT_GROUP);
scheduler.scheduleJob(jobDetail, trigger);

就是这样。这段代码将导致动作无限期地每秒执行一次,拦截器将全部触发并注入依赖项。当然,任何依赖于 Servlet 对象(如 HttpServletRequest)的逻辑或拦截器都不会正常运行,但是无论如何在 servlet 上下文之外安排这些操作是没有意义的。

【讨论】:

谢谢。我花了一段时间来解开这个问题,但它现在正在工作。本质上,它归结为创建一个类 (QuartzActionProxyFactory) 并配置 struts 以将其实例化为它的代理工厂。然后在创建实例后立即静态获取实例(请参阅 QuartzActionProxyFactory 的构造函数),以便可以在其他地方使用它。预计在设置适当的堆栈等方面会遇到一些麻烦,因此这些无请求、无会话的操作将起作用。【参考方案2】:

您不需要 HttpServletRequest 在 freemarker 中格式化电子邮件。请参阅以下答案。

Create multi-part message in MIME format Freemarker template via Spring 3 JavaMail

对于发送邮件,您可以使用 spring 将邮件组件注入到您的 Quartz 作业中。即使有一个 RequestContextHolder 类来检索 HttpServlet 请求,您也不会从 Quartz 作业中获取 HttpServletRequest。

【讨论】:

以上是关于如何从 Quartz 作业中执行 Struts 2 Action。如何获得容器?的主要内容,如果未能解决你的问题,请参考以下文章

确保Spring Quartz作业执行不重叠

Quartz 之 Job参数 和 Job状态

quartz作业调度的应用和原理

Quartz使用

如何使用 Quartz 安排作业在一天内多次但固定的时间运行

quartz任务调度框架与spring整合