java - 无法让 Quartz 任务在 Spring web mvc 中运行

标签 java spring spring-mvc tomcat quartz-scheduler

我是一名开发人员,在拥有大约 5 年的 .NET 经验后又回到了 Java。我正在开发一个 Web 应用程序项目,我需要更新数据库并每 15 分钟发布一次 http 帖子。我整理了以下内容,但尽管它在 tomcat 中显示了一个线程已启动以执行 Quartz 作业,但它什么也没做。数据库未更新。作为记录,我拥有的 JPA hibernate 实现有效。我已经测试过它,因为它也有一个 CRUD 表单。

对于计划任务,我有两个类:我有一个扩展 ServletContextListener 的 ContextListener 类。它将注册到 servlet 容器或 AS,并在它启动时被安排启动。在这种情况下,AS 是 Tomcat 7.0.57。我还有一个代表作业的 ApiKeyExpirationJob 类。我还在 web.xml 文件中添加了一些配置,以指示 Tomcat 在它启动时启 Action 业。

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();
                }
            }
        } 
    }
}

Web 应用程序使用基于 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;
        }*/
}

初始化类如下:

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 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 没有产生任何结果,因为我设置的断点没有命中。有人可以帮我解决这个问题吗?

非常感谢。

最佳答案

我最终使用第二种方法取得了成功:使用 spring 调度。因为我在我的 bean 定义中使用配置类而不是 xml 配置文件中的 xml namespace 或 bean 定义,所以我删除了 <task:annotaion-driven></task:annotaion-driven> xml 命名空间从 web.xml 文件并将其替换为我的 WebAppConfig 类中的注释,我的 @Configuration在这种情况下是类。

然后我继续将 ApiExpirationTask() 方法从下面的服务类/层移动到它的 Controller 类。我从代码片段中删除了 @Transactional 注释并修复了一些逻辑控制流问题。

@Service
public class ApiServiceImpl implements ApiService {

具有工作任务/步骤的 final方法有效并且当前在 Controller 类中声明是

@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 {

作为我将 ApiExpirationTask() 方法移动到的 Controller 类,具有 @Controller 注释,它是通用 @Component 注释的特化,WebAppConfig 类(@Configuration 类)上已经存在的 @ComponentScan 注释,执行扫描工作并定位 ApiController 类,该类包含应定期执行的任务。如果托管类未使用@Component 或其任何特化项(如@Controller)进行注释,那么我们需要在 WebAppConfig 类中声明一个@Bean 方法,以启用检测容器中已使用@Scheduled 注释的任务或应用服务器。

您可以从以下资源中获取更多信息。

http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableScheduling.html

http://javapapers.com/spring/spring-component-service-repository-controller-difference/

非常感谢。

关于java - 无法让 Quartz 任务在 Spring web mvc 中运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27589086/

相关文章:

Java:使用 double 不准确

java - 拦截器在spring中的使用

spring - 在 Spring Boot Data Rest 中向 HATEOAS 响应添加更多信息

java - 当我使用 @EnableMBeanExport 时,如何通过 Spring JMX 集成设置通知监听器映射

java - Spring 安全 java.lang.IllegalArgumentException : Failed to evaluate expression 'ROLE_ADMIN'

java - 重新设计MySQL表的主键

java - 具有缓存线程的单线程执行器

java - Spring Boot 按日期搜索数据(格式为 ZonedDateTime)

java - 无法在 JAVA API springs 中执行带有 json 值的 URL

java - 我无法将 css 文件包含到我的 Spring BOOT