定时任务quartz使用详解

2 篇文章 0 订阅
1 篇文章 0 订阅

第三章 quartz

1.Quartz是什么

  • quartz官网:http://quartz-scheduler.org
  • Quartz是一个功能丰富的开源任务调度框架,几乎可以集成任何java应用,小到单体应用,大到大型电子商务系统;
  • Quartz可以用来执行成千上万的简单或者复杂的调度任务;
  • Quartz的任务被定义为一组标准的java组件,几乎可以执行任何编程任务;
  • Quartz包含了很多企业级功能,包括JTA事务和集群等;

2.Quartz能做什么

  • 定时发送邮件、短信;

  • 定时数据同步等;

3.Quartz的特点

  • 具备强大调度功能,具有灵活的使用方式;

  • 支持集群;

4.Quartz的核心API

  1. Job:创建任务必须要实现的接口

  2. Trigger:定义了任务的执行规则

    SimpleTrigger:可以定义任务何时执行,执行频率、何时停止

    CronTrigger:使用Cron表达式灵活的定义调度时间

  3. Scheduler:调度器,使用Trigger定义的规则去调度Job

  4. JobDetail:定义任务的细节(任务名称、所属任务组)

  5. JobBuilder:用户创建任务

  6. TriggerBuilder:创建Trigger

  7. JobDataMap:JobDetail和Trigger向任务类传参

  8. JobExecutionContext:任务执行上下文;

其它分布式调度框架:当当网Elastic-Job、XXL-Job

5.使用代码测试SimpleTrigger

maven坐标

<dependency>
	<groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
  <version>2.3.0</version>
</dependency>

测试任务

  1. 每隔5秒打印一次Hello
  2. 任务三秒后执行,之后每隔5秒打印一次Hello,重复执行10次
  3. 任务三秒后执行,之后每隔5秒打印一次Hello,20秒后停止执行

开发步骤

  • 创建HelloJob任务
  • 创建JobDetail
  • 创建Trigger
  • 创建Scheduler
  • 执行调度

HelloJob.java

package com.etoak.job;
public class HelloJob implements Job {

	// 测试JobDetail给成员变量传参
	private String name;

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		LocalDateTime now = LocalDateTime.now();
		System.out.println("Hello " + now.toString() + " - " + this.name);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}

测试一:

package com.etoak.simple;
public class FirstMain {

	public static void main(String[] args) throws SchedulerException {
		// 1. 创建JobDetail
		JobKey jobKey = new JobKey("hello", "hello");
		JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) //
				.withIdentity(jobKey)//
				.build();

		// 2. 创建Trigger
		TriggerKey triggerKey = new TriggerKey("hello", "hello");
		SimpleTrigger trigger = TriggerBuilder.newTrigger()//
				.withIdentity(triggerKey)// 唯一标识
				.startNow()// 立即启动
				.withSchedule(SimpleScheduleBuilder//
						.simpleSchedule()//
						.withIntervalInSeconds(5)//
						.repeatForever()//
				).build();

		// 3. 创建Scheduler,并启动调度
		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
		scheduler.scheduleJob(jobDetail, trigger);
		scheduler.start();
	}

}

测试二:

package com.etoak.simple;
public class SecondMain {

	public static void main(String[] args) throws SchedulerException {
		// 创建JobDetail
		JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)//
				.withIdentity("hello", "hello")//
				// 直接给成员变量传参,key就是成员变量名称
				.usingJobData("name", "zhangsan") //
				.build();

		System.out.println("当前时间:" + LocalDateTime.now());

		// 获取当前时间的毫秒数
		long currentTimeMillis = System.currentTimeMillis();
		// 3秒后的时间
		Date triggerStartTime = new Date(currentTimeMillis + 3000L);

		// 创建Trigger
		SimpleTrigger trigger = TriggerBuilder//
				.newTrigger().withIdentity("hello", "hello")//
				.startAt(triggerStartTime) // 3秒的Date
				.withSchedule(SimpleScheduleBuilder//
						.simpleSchedule()//
						.withIntervalInSeconds(5)// 每隔5秒钟
						.withRepeatCount(10) // 重复10次
				).build();

		// 创建Scheduler
		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
		scheduler.scheduleJob(jobDetail, trigger);
		scheduler.start();
	}
}

测试三:

package com.etoak.simple;
public class ThirdMain {

	public static void main(String[] args) throws SchedulerException {
		// 创建JobDetail
		JobDetail jobDetail = JobBuilder //
				.newJob(HelloJob.class) //
				.withIdentity("hello", "hello") //
				.build();

		System.out.println(LocalDateTime.now());

		// 分别获取3秒后和20秒后的时间
		long current = System.currentTimeMillis();
		Date three = new Date(current + 3000L);
		Date twenty = new Date(current + 1000L * 20);

		// 创建Trigger
		SimpleTrigger trigger = TriggerBuilder.newTrigger()//
				.withIdentity("hello", "hello")//
				.usingJobData("name", "lisi")// 给job任务的成员变量传参
				.startAt(three)// 3秒后开始
				.endAt(twenty) // 20秒后停止
				.withSchedule(SimpleScheduleBuilder//
						.simpleSchedule()//
						.withIntervalInSeconds(5)//
						.repeatForever()//
				).build();

		// 创建Scheduler
		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
		scheduler.scheduleJob(jobDetail, trigger);
		scheduler.start();
	}
}

6.Cron表达式

  • Quartz Cron表达式用于配置CronTrigger实例

  • Quartz Cron表达式由7个子表达式组成,用于描述时间的各个细节,这些表达式用空 格分隔

[秒]      [分]   [小时]  [天、日期]   [月]    [周]      [年](可选) 
seconds Minutes Hours Day-of-Month Month Day-of-Week   YEAR
时间字段是否必填取值范围允许的特殊字符
0-59, * - /
0-59, * - /
小时0-59, * - /
【天】每月的第几号1-31, * - / ? L W
1-12, * - /
周几1-7【对应周日到周六】, * - / ? L #
空或1970-2099, * - /
特殊字符解释
,表示或的关系。如用在分钟:1,5 - 表示在第一分钟和第5分钟执行
*每秒、每分、每小时、每天、每月等。在秒上写*,表示每秒钟都要执行
-表示范围。如用在秒上:1-20 - 表示在1-20秒都要执行
/表示每隔多长时间执行。用在秒上:*/5或0/5 - 表示每隔5秒执行一次
?只能用到天和周上
1.如果[天]字段上用了或确切的某一天,那么[周]只能用?
2.如果[周]字段上用了或确切的某一个周几,那么[天]只能用?
L表示每个月的最后一天或者每个月的最后星期几,[周]写6L - 表示每个月 最后一个星期5
W只能用在[天、日期]的字段,表示离这个日期最近的一个工作日 比如:
1. 25W:表示离这个月25号最近的一个工作日
如果25号是一个工作日,那么就是这天执行
如果25号是周六,那么应该是24号周五执行
如果25号是周天,那么应该是下周一26号执行
2.日期不能跨月
1W:离1号最近的工作日,如果1号是周六,那么离1号最近的工作日 应该是当月3号,而不是上个月的最后一天;
3.只能指定单一日期,不能用范围
#用在[周]上,表示每个月第几个周几,
例如在[周上]写SUN#2 - 表示每个 月第二个周天
母亲节的例子:母亲节当天8点执行一个短信发送任务 0 0 8 ? 5 SUN#2

CronTest.java

package com.etoak.cron;
public class CronTest {

	public static void main(String[] args) throws SchedulerException {

		CronTrigger trigger = TriggerBuilder.newTrigger()//
				.withIdentity("hello")//
				.usingJobData("name", "lisi")//
				.withSchedule(CronScheduleBuilder//
						.cronSchedule("0/5 * * * * ?")//
				).build();

		JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)//
				.withIdentity("hello")//
				.usingJobData("name", "zs")//
				.build();

		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
		scheduler.scheduleJob(jobDetail, trigger);
		scheduler.start();
	}
}

7.整合spring mvc

7.1整合单机版

pom.xml

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.etoak.et1912.quartz</groupId>
		<artifactId>quartz-et1912</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>quartz-springmvc</artifactId>
	<packaging>war</packaging>

	<dependencies>

		<!-- quartz -->
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
		</dependency>

		<!-- servlet-api -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
		</dependency>
		<!-- spring-webmvc -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
		</dependency>

		<!-- spring-context-support -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
		</dependency>

		<!-- thymeleaf -->
		<dependency>
			<groupId>org.thymeleaf</groupId>
			<artifactId>thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.thymeleaf</groupId>
			<artifactId>thymeleaf-spring5</artifactId>
		</dependency>

		<!-- jackson-databind -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
		</dependency>

		<!-- spring-jdbc -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
		</dependency>
		<!-- spring-tx -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
		</dependency>
	</dependencies>
	<build>
		<finalName>quartz-springmvc</finalName>
	</build>
</project>

web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <context-param>
  	<param-name>contextConfigLocation</param-name>
  	<param-value>classpath:spring-root.xml</param-value>
  </context-param>
  <listener>
  	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  
  <filter>
  	<filter-name>encoding</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>
  </filter>
  <filter-mapping>
  	<filter-name>encoding</filter-name>
  	<url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <servlet>
  	<servlet-name>dispatcher</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  	<init-param>
  		<param-name>contextConfigLocation</param-name>
  		<param-value>classpath:spring-mvc.xml</param-value>
  	</init-param>
  	<load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
  	<servlet-name>dispatcher</servlet-name>
  	<url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.2.xsd
	http://www.springframework.org/schema/mvc
	http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">
	<!-- 注解扫描 -->
	<context:component-scan base-package="com.etoak">
		<context:include-filter type="annotation"
			expression="org.springframework.stereotype.Controller" />
		<context:include-filter type="annotation"
			expression="org.springframework.web.bind.annotation.RestController" />
		<context:include-filter type="annotation"
			expression="org.springframework.web.bind.annotation.ControllerAdvice" />

		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
		<context:exclude-filter type="annotation"
			expression="org.springframework.stereotype.Repository" />
	</context:component-scan>

	<!-- 开启mvc配置 -->
	<mvc:annotation-driven></mvc:annotation-driven>

	<!-- 静态文件交给Servlet容器处理 -->
	<mvc:default-servlet-handler />

	<!-- Thymeleaf的三个Bean -->
	<bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
		<property name="prefix" value="classpath:/templates/" />
		<property name="suffix" value=".html" />
		<property name="templateMode" value="HTML" />
		<property name="characterEncoding" value="UTF-8" />
		<property name="cacheable" value="false" />
	</bean>

	<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
		<property name="templateResolver" ref="templateResolver" />
	</bean>

	<bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
		<property name="templateEngine" ref="templateEngine" />
		<property name="characterEncoding" value="UTF-8" />
	</bean>


	<mvc:view-controller path="/" view-name="index" />
	

</beans>

spring-root.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.2.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
	<!-- 注解扫描 -->
	<context:component-scan base-package="com.etoak">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
		<context:include-filter type="annotation"
			expression="org.springframework.stereotype.Repository" />

		<context:exclude-filter type="annotation"
			expression="org.springframework.stereotype.Controller" />
		<context:exclude-filter type="annotation"
			expression="org.springframework.web.bind.annotation.RestController" />
		<context:exclude-filter type="annotation"
			expression="org.springframework.web.bind.annotation.ControllerAdvice" />
	</context:component-scan>
	<!-- 导入单机版quartz配置 -->
	<import resource="classpath:standalone.xml" />
</beans>

standalone.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.2.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">


	<!-- 任务类 -->
	<bean id="emailJob" class="com.etoak.job.EmailJob" />

	<!-- jobDetail -->
	<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
		<property name="name" value="email" />
		<property name="group" value="email" />
		<!-- 任务类对象 -->
		<property name="targetObject" ref="emailJob" />
		<!-- 任务类中要执行的方法名称 -->
		<property name="targetMethod" value="send" />
	</bean>

	<!-- Crontrigger -->
	<bean id="trigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
		<property name="name" value="email" />
		<property name="group" value="email" />
		<property name="jobDetail" ref="jobDetail" />
		<property name="cronExpression" value="0/5 * * * * ?" />
	</bean>

	<!-- SchedulerFactoryBean -->
	<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="triggers">
			<list>
				<ref bean="trigger" />
			</list>
		</property>
	</bean>

</beans>

EmailJob.java

package com.etoak.job;

public class EmailJob {
	//开启服务器执行以下方法
	public void send() {
		System.out.println("send email");
	}
}

7.2集群

quartz任务集群:通过JDBC方式传播任务状态

  • 在quartz集群中,同一时刻只有一个任务执行
  • 当一个任务宕机之后,其它服务会接管这个任务

1. 创建数据库和表

数据库:使用et1912数据库

数据表:quartz已经提供

2. 拷贝quartz.properties文件到src/main/resources下

​ 文件中包含集群名称、集群实例ID如何产生、任务存储、scheduler实例键入数据时间等等

3. 配置Quartz

  1. 数据源

    这里使用HikariCP

  2. 事务管理器:DataSourceTransactionManager

  3. 配置JobDetailFactoryBean

    特点:要求任务类要继承QuartzJobBean

    属性:name、group、jobClass(Class,不是spring bean)、durability

  4. 配置CronTriggerFactoryBean

    属性:name、group、jobDetail、cronExpression

  5. 配置SchedulerFactoryBean

    属性:数据源、事务管理器、configLocation、triggers

cluster.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.2.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">

	<!-- 1. 数据源 这里使用HikariCP 2. 事务管理器:DataSourceTransactionManager 3. 配置JobDetailFactoryBean 特点:要求任务类要继承QuartzJobBean 属性:name、group、jobClass(Class,不是spring 
		bean)、durability 4. 配置CronTriggerFactoryBean 属性:name、group、jobDetail、cronExpression 5. 配置SchedulerFactoryBean 属性:数据源、事务管理器、configLocation、triggers -->
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/et1912" />
		<property name="username" value="root" />
		<property name="password" value="root" />
	</bean>

	<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
		<property name="name" value="orderJob" />
		<property name="group" value="orderJob" />
		<!-- 配置任务: jobClass的值不是spring bean,是通过反射创建的 -->
		<property name="jobClass" value="com.etoak.job.OrderJob" />
		<!-- 开启持久化 -->
		<property name="durability" value="true" />
	</bean>

	<bean id="trigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
		<property name="name" value="orderTrigger" />
		<property name="group" value="orderTrigger" />
		<property name="jobDetail" ref="jobDetail" />
		<property name="cronExpression" value="0/5 * * * * ?" />
	</bean>


<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
	<property name="dataSource" ref="dataSource" />
	<property name="transactionManager" ref="tx" />
	<property name="configLocation" value="classpath:quartz.properties" />
	<property name="triggers">
		<list>
			<ref bean="trigger" />
		</list>
	</property>

	<!-- 第一种解决Job不能使用Spring bean的问题 -->
	<property name="applicationContextSchedulerContextKey" value="spring" />
	<!-- 第二种解决Job不能使用Spring bean的问题 -->
	<property name="jobFactory">
		<bean class="com.etoak.factory.SpringJobFactory" />
	</property>
</bean>
</beans>

JobController.java

package com.etoak.controller;
@RestController
@RequestMapping("/job")
public class JobController {
	@Autowired
	Scheduler scheduler;
	
	@RequestMapping(value="/pause" ,produces = "text/plain;charset=UTF-8")
	public String pause(String name , String group)throws SchedulerException{
		TriggerKey triggerKey = new TriggerKey(name,group);
		
		if(!scheduler.checkExists(triggerKey)) {
			return "任务不存在";
		}
		scheduler.pauseTrigger(triggerKey);
		return "success";
	}	
}

SpringJobFactory.java

package com.etoak.factory;
public class SpringJobFactory extends SpringBeanJobFactory {
	//将第三方框架创建的对象重新加载到spring容器中
	@Autowired
	AutowireCapableBeanFactory ioc;

	@Override
	protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
		//调用父类方法创建(反射)任务实例
		Object object = super.createJobInstance(bundle);
		//将创建的任务重新加载到spring容器中
		ioc.autowireBean(object);
		return object;
	}
}

第一种方式解决Job不能使用Spring bean的问题

package com.etoak.job;
public class OrderJob extends QuartzJobBean {
	
	@Override
	protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
		// 1. 先获取Scheduler
		Scheduler scheduler = context.getScheduler();
		try {
			// 2. 再获取SchedulerContext
			SchedulerContext schedulerContext = scheduler.getContext();

			// 3. 再根据applicationContextSchedulerContextKey对应值获取spring容器
			ApplicationContext ioc = (ApplicationContext) schedulerContext.get("spring");

			// 4. 最后从spring容器中获取OrderService
			OrderService orderService = ioc.getBean(OrderService.class);

			System.out.println("OrderService - " + orderService);
			orderService.delOrder();
			System.out.println("删除超时订单");
		} catch (SchedulerException e) {
			e.printStackTrace();
		}
	}
}

第二种方式解决Job不能使用Spring bean的问题

package com.etoak.job;
public class OrderJob extends QuartzJobBean {	
	@Autowired
	OrderService orderService;
	@Override
	protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
		System.out.println("OrderService22222 - " + orderService);
		orderService.delOrder();
		System.out.println("删除超时订单");
	}
}

X错误集锦

  1. Caused by: org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type

引发原因:在一个bean中引用了type后边这个类型的bean,但是把 ref  写成了 value

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值