一、Quartz简介
1.quartz是开源且具有丰富特性的"任务调度库",能够集成于任何的java应用。
2.quartz主要分为三大组件,分别是任务Job、触发器Trigger以及调度器Scheduler。
quartz体系架构图:
二、quartz三大组件简介
1.任务Job:即想要调用的任务类,需要实现org.quartz.job接口,并重写execute()方法,任务调度时会执行execute()方法。
2.触发器Trigger:即执行任务的触发器,当满足什么条件时会去执行你的任务Job,主要分为根据时长间隔执行的SimpleTrigger和根据日历执行的CronTrigger。
3.调度器Scheduler:即将Trigger和Job绑定之后,根据Trigger中的设定,负责进行Job调度的组件。
其它:
1.一个调度器可以让多个触发器Trigger和任务Job绑定,而一个任务Job实例只能和一个触发器Trigger实例绑定,反过来也是如此。
三、案例入门
1.引入maven:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.2</version>
</dependency>
本文使用的是2.3.2版本,建议使用最新版本,如果需要日志可自行引入。
2.测试代码:
public class QuartzTest {
public static void main(String[] args) throws SchedulerException {
//1.调度器(Scheduler)
SchedulerFactory schedulerfactory = new StdSchedulerFactory();
//拿到调度器,调度器只需要一个即可,可以调度多个任务
Scheduler scheduler = schedulerfactory.getScheduler();
//2.Job实例(JobDetil)
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job", "default") //name:任务的名称,group:分组名
.build();
//3.触发器(Trigger)
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger", "default") //name:触发器名称,group:分组名
.startNow() //马上执行一次,默认
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(20,2))//使用Simple触发器,执行20次,间隔2秒
.build();
//4.让Job和触发器关联
scheduler.scheduleJob(jobDetail,trigger);
//5.进行任务调度
scheduler.start();
}
public static class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//打印当前时间
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = dateFormat.format(new Date());
System.out.println(date);
}
}
}
3.执行结果:
每两秒打印一次时间。
3.步骤说明:
通过上文,可以大概知道quartz的操作大概分为六步:
1.通过SchedulerFactory获取一个任务调度器Scheduler
2.实现org.quartz.job接口,并重写execute()方法为自己需要的任务调度逻辑。
3.通过JobBuilder.newJob()构建一个JobDetail(Job实例对象),并设置对应参数。
4.通过TriggerBuilder.newTrigger()构建一个触发器Trigger,并设置对应的参数。
5.通过Scheduler将JobDetail和Trigger绑定。
6.通过Scheduler的start()方法开始进行任务的调度。
四、参数传递
1.测试代码:
public class QuartzTest {
public static void main(String[] args) throws SchedulerException{
//1.调度器(Scheduler)
SchedulerFactory schedulerfactory = new StdSchedulerFactory();
//拿到调度器,调度器只需要一个即可,可以调度多个任务
Scheduler scheduler = schedulerfactory.getScheduler();
//2.Job实例(JobDetil)
JobDataMap jobDetailMap = new JobDataMap();
jobDetailMap.put("name","xuye");
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job", "default") //name:任务的名称,group:分组名
.usingJobData(jobDetailMap) //传递参数
.build();
//3.触发器(Trigger)
JobDataMap triggerMap = new JobDataMap();
triggerMap.put("age",100);
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger", "default") //name:触发器名称,group:分组名
.usingJobData(triggerMap)
.startNow() //马上执行一次,默认
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(1,2))//使用Simple触发器,执行1次,间隔2秒
.build();
//4.让调度器关联Job和触发器
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
public static class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//方式一,获取JobDetail和Trigger传递的所有参数信息,使用此方式则不能对其进行设值。
JobDataMap jobDataMap = jobExecutionContext.getMergedJobDataMap();
//方式二,获取JobDetail的Map参数
JobDataMap jobDetailMap = jobExecutionContext.getJobDetail().getJobDataMap();
//方式三,获取Trigger的Map参数
JobDataMap triggerMap = jobExecutionContext.getTrigger().getJobDataMap();
System.out.println("getMergedJobDataMap()方法中能获取到的key和value:\n"+getKeyAndValue(jobDataMap));
System.out.println("getJobDetail().getJobDataMap()方法中能获取到的key和value:\n"+getKeyAndValue(jobDetailMap));
System.out.println("getTrigger().getJobDataMap()方法中能获取到的key和value:\n"+getKeyAndValue(triggerMap));
}
private String getKeyAndValue(Map<String,Object> map) {
StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, Object>> entries = map.entrySet();
for(Map.Entry<String, Object> entry : entries) {
sb.append(entry.getKey()+":"+entry.getValue().toString()+",");
}
return sb.toString();
}
}
}
2.执行结果:
3.说明:
1.通过使用 JobDataMap对象,在JobBuilder和TriggerBuilder中调用usingJobData方法将JobDataMap设置进去。
2.在org.quartz.job实现类中通过JobExecutionContext上文下环境获取对应的参数,主要有以下3个方法,
1)jobExecutionContext.getMergedJobDataMap():
获取JobBuilder和TriggerBuilder中两个的Map参数,注意使用该方式只能获取值,不能设置值,因为是无效的。
2)jobExecutionContext.getJobDetail().getJobDataMap():
获取JobDetail的Map参数,只能获取JobDetail的。
3)jobExecutionContext.getTrigger().getJobDataMap():
获取Trigger的Map参数,只能获取Trigger的。
3.建议:如果只获取值,则使用jobExecutionContext.getMergedJobDataMap(),如果需要获取值,并把值传递到下一个任务调度中,请使用JobDetail或Trigger的对应参数。
4.jobExecutionContext.getMergedJobDataMap()不能设置原理说明:每次任务调度时,都会创建一个JobExecutionContext,而创建后会把JobDetail或Trigger的对应参数全部设置到一个新的Map中,如果进行设值,则下次创建的还是新创建一个Map,上一个JobExecutionContext的设值则变为无效。
五、JobDataMap持久化
1.测试代码:
public class QuartzTest {
public static void main(String[] args) throws SchedulerException{
//1.调度器(Scheduler)
SchedulerFactory schedulerfactory = new StdSchedulerFactory();
//拿到调度器,调度器只需要一个即可,可以调度多个任务
Scheduler scheduler = schedulerfactory.getScheduler();
//2.Job实例(JobDetil)
JobDataMap jobDetailMap = new JobDataMap();
jobDetailMap.put("count",0);
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job", "default") //name:任务的名称,group:分组名
.usingJobData(jobDetailMap) //传递参数
.build();
//3.触发器(Trigger)
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger", "default") //name:触发器名称,group:分组名
.startNow() //马上执行一次,默认
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(20,2))//使用Simple触发器,执行20次,间隔2秒
.build();
//4.让调度器关联Job和触发器
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
public static class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//获取JobDetail的Map参数
JobDataMap jobDetailMap = jobExecutionContext.getJobDetail().getJobDataMap();
Integer count = (Integer) jobDetailMap.get("count");
++count;
System.out.println("当前执行次数:"+count);
jobDetailMap.put("count",count);
}
}
}
2.执行结果:
3.说明:
1.我们想要统计执行次数,但每次获取到的JobDetail参数Map都是新的。
2.如果想要JobDetail和Trigger的Map参数都是持久化,只需要在该Job实例的类上加上注@PersistJobDataAfterExecution即可。
3.修改如下:
4.新的测试结果:
5.JobDetail和Trigger的效果一致,Trigger代码就不多写,如果使用jobExecutionContext.getMergedJobDataMap(),只能获取JobDetail和Trigger的Map数据,并不能设值,上文已经说明原理,可自行测试。
六、自动注入
1.测试代码:
public class QuartzTest {
public static void main(String[] args) throws SchedulerException{
//1.调度器(Scheduler)
SchedulerFactory schedulerfactory = new StdSchedulerFactory();
//拿到调度器,调度器只需要一个即可,可以调度多个任务
Scheduler scheduler = schedulerfactory.getScheduler();
//2.Job实例(JobDetil)
JobDataMap jobDetailMap = new JobDataMap();
jobDetailMap.put("name","xuye");
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job", "default") //name:任务的名称,group:分组名
.usingJobData(jobDetailMap) //传递参数
.build();
//3.触发器(Trigger)
JobDataMap triggerMap = new JobDataMap();
triggerMap.put("age",100);
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger", "default") //name:触发器名称,group:分组名
.usingJobData(triggerMap)
.startNow() //马上执行一次,默认
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(1,2))//使用Simple触发器,执行1次,间隔2秒
.build();
//4.让调度器关联Job和触发器
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
public static class MyJob implements Job {
private String name;
private Integer age;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("name:"+name+",age:"+age);
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
}
}
2.执行结果:
3.说明:
1.如需使用自动注入,只需要org.quartz.job实现类中提供和其对应的属性名的set方法即可。
七、常用API
1.StdSchedulerFactory
方法名 | 说明 |
| 获取一个调度器 |
| 初始化SchedulerFactory |
| 获取所有的Scheduler |
2.Scheduler
方法名 | 说明 |
| 将JobDetail和Trigger绑定 |
| 通过JobKey立马执行一个Job |
| 通过JobKey立马执行一个Job,并传递参数 |
| 开始调度所有的任务,如果是暂停后调用则为重启 |
| 马上关闭所有任务 |
| 是否等待任务执行完关闭,true等待,false不等待 |
| 暂停所有任务Job,通过start()方法重启 |
| 通过JobKey暂停某个Job |
| 通过JobKey唤醒某个暂停的Job |
| 暂停某个Trigger的所有Job |
| 唤醒某个暂停的Trigger的Job |
3.JobBuilder
方法名 | 说明 |
| 加载Job的实例 |
| 设置该 |
| 传递参数,通过key和value |
| 传递参数,通过JobDataMap |
| 按照之前的参数设置,构建一个JobDetail |
4.JobDetail
方法名 | 说明 |
| 获取该JobDetail的JobDataMap |
| 获取该JobDetail的JobKey |
5.TriggerBuilder
方法名 | 说明 |
| 加载一个Trigger |
| 设置该 |
| 传递参数,通过key和value |
| 传递参数,通过JobDataMap |
| 关联一个ScheduleBuilder,通常是SimpleScheduleBuilder或CronScheduleBuilder |
| 按照之前的参数设置,构建一个Trigger |
6.Trigger
方法名 | 说明 |
| 获取该JobDetail的JobDataMap |
| 获取下一次执行时间 |
| 获取该Trigger的TriggerKey |
| 获取结束时间 |
| 获取开始时间 |
7.SimpleScheduleBuilder
方法名 | 说明 |
| 构建一个重复执行,按秒 |
| 重复执行,间隔按秒,并且执行一定的次数。 |
| |
| 重复执行的次数,注意:如果是1,则执行2次,因为运行立马执行一次,以此类推。 |
8.CronScheduleBuilder
方法名 | 说明 |
| 根据cron表达式构建一个CronScheduleBuilder |
9.CronExpression
方法名 | 说明 |
| 校验Cron表达式 |
| 构造函数,检验一个Cron表达式,如果不通过则抛出异常,该异常信息包含不通过的原因 |
八、Cron表达式
Cron表达式被用来配置CronTrigger实例,Cron表达式是一个由7个子表达式(第7个可以省略)组成的字符串。一般格式如下:* * * * * ?或* * * * * ?*,每个子表达式的代表一个日期细节,具体如下:
位置 | 说明 | 允许值 | 允许的特殊字符 |
1 | Seconds 秒 | 0-59 | , / * - |
2 | Minutes 分钟 | 0-59 | , / * - |
3 | Hours 小时 | 0-23 | , / * - |
4 | Day-of-Month 月中的天 | 1-31 | , / * - ? L W C |
5 | Month 月 | 1-12或英文JAN-DEC | , / * - |
6 | Day-of-Week 周中的天 | 1-7或英文SUN-SAT | , / * - ? L C # |
7 | Year (optional field) 年(可选) | 1970-2099(一般不写) | , / * - |
特殊字符含义说明:
字符 | 含义 |
,(逗号) | 表示多个值,如秒中使用逗号分隔5,10,20,则表示在5秒、10秒、20秒执行,理解为或。 |
? | 表示不指定值,用于第四(月中的天)和第六位(周中的天),因为第四位和第六位是冲突的,如果第四位有值则第六位必须为?,反过来也是如此。 |
* | 表示所有可能的值,如果秒中使用*则表示60秒的每一秒都执行,小时上使用*则表示24小时的每个小时都执行。 |
- | 表示区间,如小时上用6-8,则表示6,7,8点都会执行。 |
/ | 表示间隔,如在秒中使用0/20,则表示每隔20秒执行一次。 |
# | 表示月中的第几个周几,如6#3,表示该月第三周的周五(国外的周日=我们的周一) |
W | 指定离给定日期最近的工作日(周一到周五) |
L | 用在第四位(月中的天)表示一个月中的最后一天,用在第六位(周中的天)表示该月最后一个星期X,如用在第六位6L,则表示这个月的最后一个周五 |
九、Trigger触发器
Trigger触发器主要分为两大类:Simple触发器(通过SimpleScheduleBuilder关联)和Cron触发器(通过CronScheduleBuilder关联),上文案例中已经介绍了SimpleScheduleBuilder的使用和API设置,下面看CronScheduleBuilder的使用。
1.测试代码:
public class QuartzTest {
public static void main(String[] args) throws SchedulerException{
//1.调度器(Scheduler)
SchedulerFactory schedulerfactory = new StdSchedulerFactory();
//拿到调度器,调度器只需要一个即可,可以调度多个任务
Scheduler scheduler = schedulerfactory.getScheduler();
//2.Job实例(JobDetil)
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job", "default") //name:任务的名称,group:分组名
.build();
//3.触发器(Trigger)
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger", "default") //name:触发器名称,group:分组名
.startNow() //马上执行一次
.withSchedule(CronScheduleBuilder.cronSchedule("0/3 * * * * ?"))//使用Cron触发器
.build();
//4.让Job和触发器关联
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
public static class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//打印当前时间
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = dateFormat.format(new Date());
System.out.println(date);
}
}
}
2.执行结果:
cron表达式为:0/3 * * * * ? 即从0秒开始,每间隔3秒执行一次。
十、StdSchedulerFactory的配置
上文中介绍了StdSchedulerFactory的API,其中就有一个initialize(Properties props)方法,对StdSchedulerFactory进行初始化,常用的配置的属性名和值如下:
属性名 | 说明 |
org.quartz.threadPool.class | 使用哪个线程池(要实现org.quartz.spi.ThreadPool接口的线程池),一般用org.quartz.simpl.SimpleThreadPool |
org.quartz.threadPool.threadCount | 线程池线程数量,根据自己情况定,最好不要超过100 |
org.quartz.threadPool.threadPriority | 线程优先级,1-10,数字越小优先级越高,一般用5即可。 |
| 调度器的名字 |
| 为调度器创建一个Key值,这个Key必须在所有调度器中唯一,一般填写AUTO,让quartz自动生成。 |
| 任务调度存储(需要实现org.quartz.spi.JobStore接口的实例),一般用org.quartz.impl.jdbcjobstore.JobStoreTX |
十一、工具类
上文大致把quartz基本内容概述完毕,下一篇博客将会进行高级内容概述,在此之前,先奉上本人自己写的一个quartz任务调度工具类,能够及其方便的根据间隔或corn表达式调度任意一个类的某个方法,代码如下:
public class SchedulerUtils {
private static StdSchedulerFactory schedulerfactory = new StdSchedulerFactory();
public static final int SECONDS = 1;
public static final int MINUTES = 2;
public static final int HOURS = 3;
private static String CLASS_KEY = "CLASS-KEY";
private static String METHOD_KEY = "METHOD-KEY";
private static String PARAMS_KEY = "PARAMS-KEY";
/**
* 初始化StdSchedulerFactory
* @param map 初始化参数
* @throws SchedulerException
*/
public static void init(Map<String,String> map) throws SchedulerException {
Properties properties = new Properties();
for(Map.Entry<String, String> entry : map.entrySet()) {
properties.setProperty(entry.getKey(),entry.getValue());
}
init(properties);
}
public static void init(Properties properties) throws SchedulerException {
schedulerfactory.initialize(properties);
}
/**
* 按Cron表达式定时执行
* @param clazzName 全类名
* @param methodName 方法名
* @param args 方法参数
* @param cron cron表达式
* @throws Exception
*/
public static void executeJobCron(String clazzName, String methodName, List<Object> args,String cron) throws Exception {
executeJobCron(clazzName,methodName,args,cron,null,null);
}
/**
* 按Cron表达式定时执行
* @param clazzName 全类名
* @param methodName 方法名
* @param args 方法参数
* @param cron cron表达式
* @param startDate 开始时间,如果马上开始填写null或 new Date()即可
* @param endDate 结束时间,如果没有结束时间则填写null即可
* @throws Exception
*/
public static void executeJobCron(String clazzName, String methodName, List<Object> args,String cron,Date startDate,Date endDate) throws Exception {
JobDetail jobDetail = jobDetailHandle(clazzName, methodName, args);
Trigger trigger = cronTriggerTimeHandle(cron,startDate,endDate);
Scheduler scheduler = getScheduler();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
public static void executeJob(String clazzName, String methodName, List<Object> args, int timeUnit, int interval, Date endDate) throws Exception{
executeJob(clazzName,methodName,args,timeUnit,interval,-1,null,endDate);
}
public static void executeJob(String clazzName, String methodName, List<Object> args, int timeUnit, int interval) throws Exception{
executeJob(clazzName,methodName,args,timeUnit,interval,-1,null,null);
}
public static void executeJob(String clazzName, String methodName, List<Object> args, int timeUnit, int interval, int count) throws Exception{
executeJob(clazzName,methodName,args,timeUnit,interval,count,null,null);
}
/**
* 按照间隔时间定时执行
* @param clazzName 全类名
* @param methodName 方法名
* @param args 方法参数
* @param timeUnit 时间单位,参考本类属性定义
* @param interval 间隔长度
* @param count 执行总数,如果执行总数和结束时间都有,则按结束时间。
* @param startDate 开始时间,如果马上开始填写null或 new Date()即可
* @param endDate 结束时间,如果没有结束时间则填写null即可
* @throws Exception
*/
public static void executeJob(String clazzName, String methodName, List<Object> args, int timeUnit, int interval, int count,Date startDate,Date endDate) throws Exception {
JobDetail jobDetail = jobDetailHandle(clazzName, methodName, args);
Trigger trigger = simpleTriggerTimeHandle(timeUnit,interval, count,startDate,endDate);
Scheduler scheduler = getScheduler();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
private static JobDetail jobDetailHandle(String clazzName, String methodName, List<Object> args) throws NoSuchMethodException, ClassNotFoundException {
JobDataMap jobDataMap = new JobDataMap();
Class clazz = Class.forName(clazzName);
Method method = findMethod(clazz,methodName,args);
jobDataMap.put(CLASS_KEY,clazz);
jobDataMap.put(METHOD_KEY,method);
jobDataMap.put(PARAMS_KEY,args);
JobDetail jobDetail = JobBuilder.newJob(TaskJob.class)
.withIdentity("job")
.usingJobData(jobDataMap)
.build();
return jobDetail;
}
private static Trigger simpleTriggerTimeHandle(int timeUnit, int interval, int count, Date startDate,Date emdDate) {
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger()
.withIdentity("simpleTrigger");
if(startDate == null) {
startDate = new Date();
}
triggerBuilder.startAt(startDate);
triggerBuilder.endAt(emdDate);
if(count > 0) {
switch (timeUnit) {
case SECONDS : triggerBuilder.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(count,interval));
break;
case MINUTES : triggerBuilder.withSchedule(SimpleScheduleBuilder.repeatMinutelyForTotalCount(count,interval));
break;
case HOURS : triggerBuilder.withSchedule(SimpleScheduleBuilder.repeatHourlyForTotalCount(count,interval));
break;
}
}
return triggerBuilder.build();
}
private static Trigger cronTriggerTimeHandle(String cron,Date startDate,Date emdDate) throws ParseException {
new CronExpression(cron);
if(startDate == null) {
startDate = new Date();
}
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger")
.startAt(startDate)
.endAt(emdDate)
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
return trigger;
}
private static Scheduler getScheduler() throws SchedulerException {
return schedulerfactory.getScheduler();
}
private static Method findMethod(Class clazz,String methodName,List<Object> args) throws NoSuchMethodException {
Method method;
if(args != null && !args.isEmpty()) {
Class<?>[] paramsTypes = getMethodParamsType(args);
method = clazz.getDeclaredMethod(methodName,paramsTypes);
}else {
method = clazz.getDeclaredMethod(methodName);
}
return method;
}
private static Class<?>[] getMethodParamsType(List<Object> methodParams) {
Class<?>[] classs = new Class<?>[methodParams.size()];
int index = 0;
for (Object os : methodParams)
{
classs[index] = os.getClass();
index++;
}
return classs;
}
public static class TaskJob implements Job {
private Map<Class,Object> objectMap = new ConcurrentHashMap<Class, Object>();
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDataMap dataMap = jobExecutionContext.getMergedJobDataMap();
Class clazz = (Class) dataMap.get(CLASS_KEY);
Object object;
try {
object = getObject(clazz);
Method method = (Method) dataMap.get(METHOD_KEY);
Parameter[] parameters = method.getParameters();
if (parameters.length > 0) {
List<Object> args = (List<Object>) dataMap.get(PARAMS_KEY);
Object[] objects = args.toArray();
method.invoke(object, objects);
}else {
method.invoke(object);
}
}catch (Exception e) {
e.printStackTrace();
}
}
private Object getObject(Class clazz) throws IllegalAccessException, InstantiationException {
Object object = objectMap.get(clazz);
if(object == null) {
object = clazz.newInstance();
objectMap.put(clazz,object);
}
return object;
}
}
}