Я разработчик, вернувшийся обратно на Java через 5 лет опыта работы с.NET. Я работаю над проектом веб-приложения, где мне нужно обновить БД и сделать http-сообщение каждые 15 минут. Я собрал следующее, но хотя он показывает мне в tomcat, что поток был запущен для выполнения задания Quartz, он ничего не делает. База данных не обновляется. Для записей используется JPA спящий режим, который у меня есть. Я тестировал его, потому что есть форму CRUD.
Для запланированной задачи у меня есть два класса: у меня есть класс ContextListener, который расширяет ServletContextListener. Он будет зарегистрирован в контейнере сервлетов или AS и будет запущен при запуске. В этом случае AS является Tomcat 7.0.57. У меня также есть класс ApiKeyExpirationJob, который представляет собой работу. Я также добавил некоторую конфигурацию в файл web.xml для обучения Tomcat, чтобы начать работу, когда она начнется.
Класс ContextListener, который инициализирует задание в рамках контекста.
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.quartz.DateBuilder;
import org.quartz.DateBuilder.IntervalUnit;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.apache.log4j.Logger;
public class ContextListener implements ServletContextListener
{
/*private static final Logger LOGGER =
Logger.getLogger(ContextListener.class);*/
// Initiate a Schedule Factory
private static final SchedulerFactory schedulerFactory = new StdSchedulerFactory();
// Retrieve a scheduler from schedule factory (Lazy init)
private Scheduler scheduler = null;
@Override
public void contextDestroyed(ServletContextEvent arg0)
{
try
{
if (scheduler != null && scheduler.isStarted())
scheduler.shutdown();
}
catch (SchedulerException e)
{
//LOGGER.error(e);
}
}
@Override
public void contextInitialized(ServletContextEvent arg0)
{
//LOGGER.info("----- Initializing quartz -----");
try
{
scheduler = schedulerFactory.getScheduler();
// Initiate JobDetail with job name, job group, and executable job class
JobDetail jobDetail = JobBuilder.newJob(ApiKeyExpirationJob.class)
.withIdentity("db_refresher", "refresher")
.build();
// Initiate SimpleTrigger with its name and group name.
SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInHours(1).repeatForever())
.startAt(DateBuilder.futureDate(15, IntervalUnit.MINUTE))
.build();
scheduler.scheduleJob(jobDetail, simpleTrigger);
// start the scheduler
//scheduler.start();
}
catch (SchedulerException se)
{
//LOGGER.error(se);
}
catch (Exception e)
{
//LOGGER.error(e);
}
}
}
Класс ApiKeyExpirationJob
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
//import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import com.google.gson.Gson;
import com.x.apimanager.exception.ApiNotFound;
import com.x.apimanager.model.Api;
import com.x.apimanager.service.ApiService;
@SuppressWarnings("deprecation")
public class ApiKeyExpirationJob implements Job
{
/* private static final Logger LOGGER =
Logger.getLogger(ApiKeyExpirationJob.class);*/
@Autowired
private ApiService apiService;
// This is the method that will be called by the scheduler when the trigger fires the job.
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException
{
// Do you scheduled task here. This is usually the repetitive piece that runs for every firing of the trigger.
//LOGGER.info("Executing scheduled job");
List<Api> apiList = apiService.findAll();
for(Api api : apiList) {
//System.out.println(api.getApiKey());
//final SimpleDateFormat df = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss" );
final java.util.Calendar cal = GregorianCalendar.getInstance();
cal.setTime( api.getCreatedDate() );
cal.add( GregorianCalendar.MINUTE, 1 ); // date manipulation
Date dateExpectedExpiryDate = cal.getTime();
Date dateNow = new Date();
if ((dateNow.compareTo(dateExpectedExpiryDate) > 1) || (dateNow.compareTo(dateExpectedExpiryDate) == 0))
{
api.setIsExpired(true);
try {
apiService.update(api);
System.out.println("Updating API");
//post the entity as JSON
String postUrl=api.getUrl();// put in your url
Gson gson= new Gson();
HttpPost post = new HttpPost(postUrl);
StringEntity postingString = new StringEntity(gson.toJson(api));//convert your pojo to json
System.out.println(postingString.toString());
post.setEntity(postingString);
post.setHeader("Content-type", "application/json");
@SuppressWarnings("deprecation")
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(post);
} catch (ApiNotFound | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
Веб-приложение использует основанный на Java подход к аннотациям и класс WebAppConfig. Ниже приведенные java файлы конфигурации приложения. Я считаю, что это может быть, если у меня возникнут проблемы. Большинство примеров, которые я нахожу в Интернете, используют подход конфигурации xml, но, кроме преобразования некоторых классов, я не знаю, как сопоставить некоторые другие атрибуты конфигурации этому классу.
import java.util.Properties;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.hibernate.ejb.HibernatePersistence;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan("com.x.apimanager")
@PropertySource("classpath:application.properties")
@EnableJpaRepositories("com.x.apimanager.repository")
public class WebAppConfig {
private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";
private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";
@Resource
private Environment env;
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
dataSource.setUrl(env.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
dataSource.setUsername(env.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
dataSource.setPassword(env.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
entityManagerFactoryBean.setPackagesToScan(env.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));
entityManagerFactoryBean.setJpaProperties(hibProperties());
return entityManagerFactoryBean;
}
private Properties hibProperties() {
Properties properties = new Properties();
properties.put(PROPERTY_NAME_HIBERNATE_DIALECT, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
return properties;
}
@Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
@Bean
public UrlBasedViewResolver setupViewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setPrefix("/WEB-INF/pages/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename(env.getRequiredProperty("message.source.basename"));
source.setUseCodeAsDefaultMessage(true);
return source;
}
@Bean
public org.apache.activemq.ActiveMQConnectionFactory connectionFactory() {
org.apache.activemq.ActiveMQConnectionFactory connectionFactory = new org.apache.activemq.ActiveMQConnectionFactory();
connectionFactory.setBrokerURL("tcp://localhost:61616");
return connectionFactory;
}
@Bean
public org.springframework.jms.core.JmsTemplate jmsTemplate() {
org.springframework.jms.core.JmsTemplate jmsTemplate = new org.springframework.jms.core.JmsTemplate(connectionFactory());
jmsTemplate.setDefaultDestinationName("apiqueue");
return jmsTemplate;
}
/*@Bean
public com.x.apimanager.scheduler.ContextListener contextListener(){
com.x.apimanager.scheduler.ContextListener contextListener = new com.x.apimanager.scheduler.ContextListener();
contextListener.
}*/
/* @SuppressWarnings("deprecation")
@Bean
public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter()
{
final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
HttpMessageConverter<?>[] httpMessageConverter = { mappingJacksonHttpMessageConverter };
String[] supportedHttpMethods = { "POST", "GET", "HEAD" };
annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);
return annotationMethodHandlerAdapter;
}*/
}
Класс Initializer находится ниже:
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class Initializer implements WebApplicationInitializer {
private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
public void onStartup(ServletContext servletContext)
throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(WebAppConfig.class);
servletContext.addListener(new ContextLoaderListener(ctx));
ctx.setServletContext(servletContext);
Dynamic servlet = servletContext.addServlet(DISPATCHER_SERVLET_NAME,
new DispatcherServlet(ctx));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
Я добавил следующее в файл web.xml
<context-param>
<param-name>quartz:shutdown-on-unload</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>quartz:wait-on-shutdown</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>quartz:start-scheduler-on-load</param-name>
<param-value>true</param-value>
</context-param>
<listener>
<listener-class>com.x.apimanager.scheduler.ContextListener</listener-class>
</listener>
эволюция окончательного web.xml ниже
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>apimanager</display-name>
<context-param>
<param-name>quartz:shutdown-on-unload</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>quartz:wait-on-shutdown</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>quartz:start-scheduler-on-load</param-name>
<param-value>true</param-value>
</context-param>
<listener>
<listener-class>com.x.apimanager.scheduler.ContextListener</listener-class>
</listener>
</web-app>
Вышеупомянутое не сработало, хотя tomcat начал поток каждый раз, когда он был запущен. Затем я попытался использовать аннотацию spring @Scheduled для запуска задания. Я добавил задания задания в метод void (void ApiExpirationTask()) в своем классе сервиса и аннотировал его с помощью @Scheduled и добавил файл <task:annotaion-driven></task:annotaion-driven>
в файл web.xml.
Класс обслуживания
import java.io.IOException;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import javax.annotation.Resource;
//import javax.inject.Inject;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.google.gson.Gson;
import com.x.apimanager.exception.ApiNotFound;
import com.x.apimanager.model.Api;
import com.x.apimanager.repository.ApiRepository;
@Service
public class ApiServiceImpl implements ApiService {
private static final int PAGE_SIZE = 5;
//@Inject private ApiRepository apiRepository2;
@Resource
private ApiRepository apiRepository;
public Page<Api> getApiLog(Integer pageNumber) {
PageRequest request =
new PageRequest(pageNumber - 1, PAGE_SIZE, Sort.Direction.DESC, "id");
return apiRepository.findAll(request);
}
@Override
@Transactional
public Api create(Api api) {
Api createdApi = api;
createdApi.setIsExpired(true);
//getting current date and time using Date class
//DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
Date dateobj = new Date();
//createdApi.setCreatedDate(df.format(dateobj));
//createdApi.setModifiedDate(df.format(dateobj));
createdApi.setCreatedDate(dateobj);
createdApi.setModifiedDate(dateobj);
return apiRepository.save(createdApi);
}
@Override
@Transactional
public Api findById(int id) {
return apiRepository.findOne(id);
}
@Override
@Transactional(rollbackFor=ApiNotFound.class)
public Api delete(int id) throws ApiNotFound {
Api deletedApi = apiRepository.findOne(id);
if (deletedApi == null)
throw new ApiNotFound();
apiRepository.delete(deletedApi);
return deletedApi;
}
@Override
@Transactional
public List<Api> findAll() {
return apiRepository.findAll();
}
@Override
@Transactional(rollbackFor=ApiNotFound.class)
public Api update(Api api) throws ApiNotFound {
Api updatedApi = apiRepository.findOne(api.getId());
if (updatedApi == null)
throw new ApiNotFound();
updatedApi.setApiKey(api.getApiKey());
updatedApi.setUrl(api.getUrl());
updatedApi.setModifiedDate(new Date());
return updatedApi;
}
@Scheduled(fixedDelay=60000)
@Transactional
public void ApiExpirationTask()
{
List<Api> apiList = findAll();
for(Api api : apiList) {
//System.out.println(api.getApiKey());
//final SimpleDateFormat df = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss" );
final java.util.Calendar cal = GregorianCalendar.getInstance();
cal.setTime( api.getCreatedDate() );
cal.add( GregorianCalendar.MINUTE, 1 ); // date manipulation
Date dateExpectedExpiryDate = cal.getTime();
Date dateNow = new Date();
if ((dateNow.compareTo(dateExpectedExpiryDate) > 1) || (dateNow.compareTo(dateExpectedExpiryDate) == 0))
{
api.setIsExpired(true);
try {
update(api);
System.out.println("Updating API");
//post the entity as JSON
String postUrl=api.getUrl();// put in your url
Gson gson= new Gson();
HttpPost post = new HttpPost(postUrl);
StringEntity postingString = new StringEntity(gson.toJson(api));//convert your pojo to json
System.out.println(postingString.toString());
post.setEntity(postingString);
post.setHeader("Content-type", "application/json");
@SuppressWarnings("deprecation")
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(post);
} catch (ApiNotFound | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
Изменения в web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>apimanager</display-name>
<task:annotaion-driven></task:annotaion-driven>
</web-app>
Второй подход также не работал, и он не бросает никаких исключений. Попытка пройти через Eclipse STS IDE ничего не принесла, потому что точка останова, которую я установил, не попал. Пожалуйста, кто-нибудь может мне помочь?
Большое спасибо.
В конечном итоге я добился успеха в использовании второго подхода: используя весеннее планирование. Поскольку я использовал класс Configuration для своих определений bean вместо имен пространства имен xml или определения bean-компонентов в файлах конфигурации xml, я удалил пространство имен <task:annotaion-driven></task:annotaion-driven>
xml из файла web.xml и заменил его аннотацией в моем классе WebAppConfig, мой класс @Configuration
в этом случае.
Затем я переместил метод ApiExpirationTask() из класса/уровня службы ниже в его класс Controller. Я удалил аннотацию @Transactional из фрагмента кода и исправил некоторые проблемы с логическим управлением.
@Service
public class ApiServiceImpl implements ApiService {
Окончательный метод с заданиями/шагами задания, которые работают и в настоящее время объявлены в классе контроллера,
@Scheduled(fixedDelay=60000)
public void ApiExpirationTask()
{
// Beginning of the ApiKeyExpirationJob Workflow
List<Api> apiList = apiService.findAll();
for(Api api1 : apiList) {
//logger.info(api.getApiKey());
//final SimpleDateFormat df = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss" );
final java.util.Calendar cal = GregorianCalendar.getInstance();
cal.setTime( api1.getCreatedDate() );
cal.add( GregorianCalendar.MINUTE, 1 ); // date manipulation
Date dateExpectedExpiryDate = cal.getTime();
Date dateNow = new Date();
if ((dateNow.compareTo(dateExpectedExpiryDate) >= 0) && (api1.getIsExpired() == false))
{
api1.setIsExpired(true);
try {
//TODO: Fix apache commons logging to work with SLF4J. Then remove the out.println statements.
//logger.info("Updating API");
System.out.println("Updating API");
apiService.update(api1);
//post the entity as JSON
String postUrl=api1.getUrl();// put in your url
Gson gson= new Gson();
HttpPost post = new HttpPost(postUrl);
StringEntity postingString = new StringEntity(gson.toJson(api1));//convert your pojo to json
//logger.info("API Entity in json representaton is: " + gson.toJson(api1));
System.out.println("API Entity in json representaton is: " + gson.toJson(api1));
//logger.info(postingString.toString());
post.setEntity(postingString);
post.setHeader("Content-type", "application/json");
@SuppressWarnings("deprecation")
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(post);
//logger.info(response.getStatusLine().toString());
System.out.println(response.getStatusLine().toString());
} catch (IOException | ApiNotFound e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//End of ApiKeyExpirationJob Workflow
Класс @Configuration
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan("com.x.apimanager")
@PropertySource("classpath:application.properties")
@EnableJpaRepositories("com.x.apimanager.repository")
@EnableScheduling
public class WebAppConfig {
Как класс Controller, к которому я переместил метод ApiExpirationTask(), имеет аннотацию @Controller, которая является специализацией общей аннотации @Component, уже существующей аннотации @ComponentScan в классе WebAppConfig (класс @Configuration), выполняет ли задание сканирования и расположение класса ApiController, который содержит задачу, которая должна выполняться с интервалами. Если класс хостинга не аннотируется с @Component или любой из его специализаций, например @Controller, тогда нам нужно объявить метод @Bean в классе WebAppConfig, чтобы включить обнаружение задачи, которая была аннотирована с помощью @Scheduled в контейнере или сервер приложений.
Вы можете получить дополнительную информацию из следующих ресурсов.
http://javapapers.com/spring/spring-component-service-repository-controller-difference/
Большое спасибо.