Spring Boot使用CommandLineRunner接口完成资源初始化

1 简介

1.1 应用场景

 在应用服务启动时,需要在所有Bean生成之后,加载一些数据和执行一些应用的初始化。例如:删除临时文件,清楚缓存信息,读取配置文件,数据库连接,这些工作类似开机自启动的概念,CommandLineRunner、ApplicationRunner 接口是在容器启动成功后的最后一步回调(类似开机自启动)。

1.2 CommandLineRunner接口

CommandLineRunner源码实现如下:

package org.springframework.boot;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
 * Interface used to indicate that a bean should run when it is contained within
 * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 * 

* If you need access to {@link ApplicationArguments} instead of the raw String array * consider using {@link ApplicationRunner}. * * @author Dave Syer * @see ApplicationRunner */ @FunctionalInterface public interface CommandLineRunner { /** * Callback used to run the bean. * @param args incoming main method arguments * @throws Exception on error */ void run(String... args) throws Exception; }

 对该接口的注释可以看到如下的含义:

该接口用来指明:当一个bean包含在SpringApplication内,该bean就应当执行。可以在相同的应用上下文定义多个这样的bean。多个bean的先后执行顺序使用@Order注解确定。

1.3 ApplicationRunner接口

ApplicationRunner接口源码定义如下:

package org.springframework.boot;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
 * Interface used to indicate that a bean should run when it is contained within
 * a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 *
 * @author Phillip Webb
 * @since 1.3.0
 * @see CommandLineRunner
 */
@FunctionalInterface
public interface ApplicationRunner {

   /**
    * Callback used to run the bean.
    * @param args incoming application arguments
    * @throws Exception on error
    */
   void run(ApplicationArguments args) throws Exception;

}

 在对该接口的注释中,可以看到两个接口的应用场景,甚至注释都是完全一样的。唯一的区别是接口中的函数run的参数,一个是与main函数同样的(String[] args),而另外一个是ApplicationArgumens类型。在一般情况下,开发时是不需要添加命令行参数的,因此两个接口的区别对于这样的场景也就完全一样了。但如果真的需要类型–foo=bar的option arguments,为了方便起见,可以使用ApplicationRunner来读取类似的参数。

1.4 @Order注解

 Order注解的源码实现如下:

package org.springframework.core.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.Ordered;

/**
 * {@code @Order} defines the sort order for an annotated component.
 *
 * 

The {@link #value} is optional and represents an order value as defined in the * {@link Ordered} interface. Lower values have higher priority. The default value is * {@code Ordered.LOWEST_PRECEDENCE}, indicating lowest priority (losing to any other * specified order value). * *

NOTE: Since Spring 4.0, annotation-based ordering is supported for many * kinds of components in Spring, even for collection injection where the order values * of the target components are taken into account (either from their target class or * from their {@code @Bean} method). While such order values may influence priorities * at injection points, please be aware that they do not influence singleton startup * order which is an orthogonal concern determined by dependency relationships and * {@code @DependsOn} declarations (influencing a runtime-determined dependency graph). * *

Since Spring 4.1, the standard {@link javax.annotation.Priority} annotation * can be used as a drop-in replacement for this annotation in ordering scenarios. * Note that {@code @Priority} may have additional semantics when a single element * has to be picked (see {@link AnnotationAwareOrderComparator#getPriority}). * *

Alternatively, order values may also be determined on a per-instance basis * through the {@link Ordered} interface, allowing for configuration-determined * instance values instead of hard-coded values attached to a particular class. * *

Consult the javadoc for {@link org.springframework.core.OrderComparator * OrderComparator} for details on the sort semantics for non-ordered objects. * * @author Rod Johnson * @author Juergen Hoeller * @since 2.0 * @see org.springframework.core.Ordered * @see AnnotationAwareOrderComparator * @see OrderUtils * @see javax.annotation.Priority */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Documented public @interface Order { /** * The order value. *

Default is {@link Ordered#LOWEST_PRECEDENCE}. * @see Ordered#getOrder() */ int value() default Ordered.LOWEST_PRECEDENCE; }

 可以看到该注解的作用主要是用来定义被注解的组件的排序顺序。

注意:Lower values have higher priority。值越小越先执行。

1.5 CommandLineRunner和ApplicationRunner区别

 从上面的分析可以看出,CommandLineRunner和ApplicationRunner接口的作用是完全一致的,唯一不同的则是接口中待实现的run方法,其中CommandLineRunner的run方法参数类型与main一样是原生的String[] 类型,而ApplicationRunner的run方法参数类型为ApplicationArguments类型。
ApplicationArguments类型源码定义如下:

package org.springframework.boot;

import java.util.List;
import java.util.Set;

/**
 * Provides access to the arguments that were used to run a {@link SpringApplication}.
 * 提供用来运行一个SpringApplication的参数。
 * @author Phillip Webb
 * @since 1.3.0
 */
public interface ApplicationArguments {

   /**
    * Return the raw unprocessed arguments that were passed to the application.
    * @return the arguments
    */
   String[] getSourceArgs();

   /**
    * Return the names of all option arguments. For example, if the arguments were
    * "--foo=bar --debug" would return the values {@code ["foo", "debug"]}.
    * @return the option names or an empty set
    */
   Set<String> getOptionNames();

   /**
    * Return whether the set of option arguments parsed from the arguments contains an
    * option with the given name.
    * @param name the name to check
    * @return {@code true} if the arguments contain an option with the given name
    */
   boolean containsOption(String name);

   /**
    * Return the collection of values associated with the arguments option having the
    * given name.
    * 
    *
  • if the option is present and has no argument (e.g.: "--foo"), return an empty * collection ({@code []})
  • *
  • if the option is present and has a single value (e.g. "--foo=bar"), return a * collection having one element ({@code ["bar"]})
  • *
  • if the option is present and has multiple values (e.g. "--foo=bar --foo=baz"), * return a collection having elements for each value ({@code ["bar", "baz"]})
  • *
  • if the option is not present, return {@code null}
  • *
* @param name the name of the option * @return a list of option values for the given name */
List<String> getOptionValues(String name); /** * Return the collection of non-option arguments parsed. * @return the non-option arguments or an empty list */ List<String> getNonOptionArgs(); }

Spring Boot使用CommandLineRunner接口完成资源初始化_第1张图片
ApplicaitonArguments类型之间的关系如下:
Spring Boot使用CommandLineRunner接口完成资源初始化_第2张图片
1.5.1 option-argument和non-option arguments
Spring Boot使用CommandLineRunner接口完成资源初始化_第3张图片
 参见命令解释中option和non-option参数

1.5.2 SimpleCommandLineArgsParser

 由于SimpleCommandLineArgsParser的功能是ApplicationArguments实现的的核心,简单看下该类实现的源码:

package org.springframework.core.env;

/**
 * Parses a {@code String[]} of command line arguments in order to populate a
 * {@link CommandLineArgs} object.
 *
 * 

Working with option arguments

* Option arguments must adhere to the exact syntax: *
--optName[=optValue]
* That is, options must be prefixed with "{@code --}", and may or may not specify a value. * If a value is specified, the name and value must be separated without spaces * by an equals sign ("="). * *

Valid examples of option arguments

*
 * --foo
 * --foo=bar
 * --foo="bar then baz"
 * --foo=bar,baz,biz
* *

Invalid examples of option arguments

*
 * -foo
 * --foo bar
 * --foo = bar
 * --foo=bar --foo=baz --foo=biz
* *

Working with non-option arguments

* Any and all arguments specified at the command line without the "{@code --}" option * prefix will be considered as "non-option arguments" and made available through the * {@link CommandLineArgs#getNonOptionArgs()} method. * * @author Chris Beams * @since 3.1 */
class SimpleCommandLineArgsParser { /** * Parse the given {@code String} array based on the rules described {@linkplain * SimpleCommandLineArgsParser above}, returning a fully-populated * {@link CommandLineArgs} object. * @param args command line arguments, typically from a {@code main()} method */ public CommandLineArgs parse(String... args) { CommandLineArgs commandLineArgs = new CommandLineArgs(); for (String arg : args) { if (arg.startsWith("--")) { String optionText = arg.substring(2, arg.length()); String optionName; String optionValue = null; if (optionText.contains("=")) { optionName = optionText.substring(0, optionText.indexOf('=')); optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length()); } else { optionName = optionText; } if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) { throw new IllegalArgumentException("Invalid argument syntax: " + arg); } commandLineArgs.addOptionArg(optionName, optionValue); } else { commandLineArgs.addNonOptionArg(arg); } } return commandLineArgs; } }

在程序执行过程中,SimpleCommandLineArgsParser会把程序的参数分解成option arguments和nonoption arguments,然后放入CommandLineArgs,
在这里插入图片描述
CommandLineArgs类的实现如下:

package org.springframework.core.env;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.lang.Nullable;

/**
 * A simple representation of command line arguments, broken into "option arguments" and
 * "non-option arguments".
 *
 * @author Chris Beams
 * @since 3.1
 * @see SimpleCommandLineArgsParser
 */
class CommandLineArgs {

   private final Map<String, List<String>> optionArgs = new HashMap<>();
   private final List<String> nonOptionArgs = new ArrayList<>();

   /**
    * Add an option argument for the given option name and add the given value to the
    * list of values associated with this option (of which there may be zero or more).
    * The given value may be {@code null}, indicating that the option was specified
    * without an associated value (e.g. "--foo" vs. "--foo=bar").
    */
   public void addOptionArg(String optionName, @Nullable String optionValue) {
      if (!this.optionArgs.containsKey(optionName)) {
         this.optionArgs.put(optionName, new ArrayList<>());
      }
      if (optionValue != null) {
         this.optionArgs.get(optionName).add(optionValue);
      }
   }

   /**
    * Return the set of all option arguments present on the command line.
    */
   public Set<String> getOptionNames() {
      return Collections.unmodifiableSet(this.optionArgs.keySet());
   }

   /**
    * Return whether the option with the given name was present on the command line.
    */
   public boolean containsOption(String optionName) {
      return this.optionArgs.containsKey(optionName);
   }

   /**
    * Return the list of values associated with the given option. {@code null} signifies
    * that the option was not present; empty list signifies that no values were associated
    * with this option.
    */
   @Nullable
   public List<String> getOptionValues(String optionName) {
      return this.optionArgs.get(optionName);
   }

   /**
    * Add the given value to the list of non-option arguments.
    */
   public void addNonOptionArg(String value) {
      this.nonOptionArgs.add(value);
   }

   /**
    * Return the list of non-option arguments specified on the command line.
    */
   public List<String> getNonOptionArgs() {
      return Collections.unmodifiableList(this.nonOptionArgs);
   }

}

2 CommandLineRunner完成资源初始化

2.1 背景介绍

 在项目运行的过程中,项目会事先订阅一些关注的事件,大的方向分为门禁事件和监控点事件,其中上报上来的门禁事件的基本信息为门禁点信息,而不是门禁设备的唯一标识,为了解析的方便,需要把上报上来的门禁点事件和门禁设备唯一标识、IP信息关联起来,这表示,在项目启动之后,当所有bean都完成了加载,需要在项目开始提供其他REST服务之前,把门禁点到门禁设备唯一标识、IP等信息关联起来。而这个关联的获取,需要类似开机自启动的功能一样,需要项目一启动,就获取这个关联。
 其中,获取所有门禁设备的基本信息可以通过AcsDeviceController获取,而所有门禁点信息的获取通过AcsDoorController获取。
2.2 数据类型关系
调用获取所有门禁设备信息,可以获得形似如下的数据:

{
    "msg": "success",
    "code": "0",
    "data": {
        "list": [
            {
                "regionIndexCode": "a6745925-0415-4853-9815-bb9707e2ad8b",
                "acsDevTypeCode": "201933568",
                "createTime": "2019-04-11T20:10:16.184+08:00",
                "acsDevTypeDesc": "DS-K1T604MF",
                "acsDevName": "门禁一体机1",
                "acsDevIndexCode": "71a9ea67d58a43db8c5dadfe2197a4db",
                "updateTime": "2019-04-11T20:18:01.683+08:00",
                "acsDevIp": "192.168.1.103",
                "acsDevPort": "8000",
                "treatyType": "hiksdk_net"
            },
            {
                "regionIndexCode": "a6745925-0415-4853-9815-bb9707e2ad8b",
                "acsDevTypeCode": "201933568",
                "createTime": "2019-04-11T20:12:06.137+08:00",
                "acsDevTypeDesc": "DS-K1T604MF",
                "acsDevName": "门禁一体机3",
                "acsDevIndexCode": "13891ae9f6454782a208504e72ba2ad8",
                "updateTime": "2019-04-11T20:12:07.876+08:00",
                "acsDevIp": "192.168.1.105",
                "acsDevPort": "8000",
                "treatyType": "hiksdk_net"
            }
        ]
    }
}

可以看到所有的门禁设备均包含设备的唯一标识acsDevIndexCode、acsDevName门禁设备名称、门禁设备IP地址acsDevIp,门禁设备端口acsDevPort。
而获取所有的门禁点接口可以获取如下的内容:

{
    "msg": "success",
    "code": "0",
    "data": {
        "list": [
            {
                "regionIndexCode": "a6745925-0415-4853-9815-bb9707e2ad8b",
                "channelNo": "1",
                "createTime": "2019-04-11T20:12:06.166+08:00",
                "doorName": "门禁一体机3_门1",
                "doorIndexCode": "a87022481f8242b29a4bf35a57edc004",
                "acsDevIndexCode": "13891ae9f6454782a208504e72ba2ad8",
                "channelType": "door",
                "updateTime": "2019-04-11T20:12:16.012+08:00",
                "doorNo": "1"
            },
            {
                "regionIndexCode": "a6745925-0415-4853-9815-bb9707e2ad8b",
                "channelNo": "1",
                "createTime": "2019-04-11T20:10:17.826+08:00",
                "doorName": "门禁一体机1_门1",
                "doorIndexCode": "139dcaecc5ba4b9fb97889c2f2234e79",
                "acsDevIndexCode": "71a9ea67d58a43db8c5dadfe2197a4db",
                "channelType": "door",
                "updateTime": "2019-04-11T20:12:16.012+08:00",
                "doorNo": "1"
            }
        ]
    }
}

 可以看到获取到的门禁点信息,会同时包含门禁设备的唯一标识字段,因此可以根据该字段门禁点唯一标识获得门禁点到门禁设备基本信息的映射。
当系统订阅了相关的事件之后,可以收到的事件报文格式如下:

{
	"method": "OnEventNotify",
	"params": {
		"ability": "event_acs",
		"events": [{
			"data": {
				"ExtAccessChannel": 0,
				"ExtEventAlarmInID": 0,
				"ExtEventAlarmOutID": 0,
				"ExtEventCardNo": "1874193998",
				"ExtEventCaseID": 0,
				"ExtEventCode": 197634,
				"ExtEventCustomerNumInfo": {
					"AccessChannel": 0,
					"EntryTimes": 0,
					"ExitTimes": 0,
					"TotalTimes": 0
				},
				"ExtEventDoorID": 1,
				"ExtEventIDCardPictureURL": "",
				"ExtEventIdentityCardInfo": {
					"Address": "",
					"Birth": "",
					"EndDate": "",
					"IdNum": "",
					"IssuingAuthority": "",
					"Name": "",
					"Nation": 0,
					"Sex": 0,
					"StartDate": "",
					"TermOfValidity": 0
				},
				"ExtEventInOut": 1,
				"ExtEventLocalControllerID": 0,
				"ExtEventMainDevID": 1,
				"ExtEventPersonNo": "0",
				"ExtEventPictureURL": "/pic?=d2=i689z260ds986-125mfep=t9i2i*d1=*ipd1=*isd8=*dbec775bf-84fbc12-484868-82i167*e172ed5",
				"ExtEventReaderID": 1,
				"ExtEventReaderKind": 0,
				"ExtEventReportChannel": 0,
				"ExtEventRoleID": 0,
				"ExtEventSubDevID": 0,
				"ExtEventSwipNum": 0,
				"ExtEventType": 0,
				"ExtEventVerifyID": 0,
				"ExtEventWhiteListNo": 0,
				"ExtReceiveTime": "1548828922319128",
				"Seq": 0,
				"svrIndexCode": "b1cded2b-2fbc-4255-aa9d-45162bfa23dd"
			},
			"eventId": "6B15F0C9947949D5BF271327C66BD658",
			"eventType": 197634,
			"eventTypeName": "acs.acs.eventType.wrongNoSuchCard",
			"happenTime": "2019-01-30T12:44:59.000+08:00",
			"srcIndex": "92ca80c786b843058e764f7fda863ae1",
			"srcName": "10.13.65.181_门1",
			"srcParentIndex": "de948e1856f74253b65848f4bef3fb03",
			"srcType": "door",
			"status": 0,
			"timeout": 0
		}],
		"sendTime": "2019-01-30T14:15:22.000+08:00"
	}
}

关于门禁事件上报报文格式参见一卡通应用服务

2.3 实现过程

Spring Boot使用CommandLineRunner接口完成资源初始化_第4张图片
在建立映射的过程中,只需要关注门禁设备的唯一标识,ip,端口,门禁设备名称。门禁点则只需要关注门禁点唯一标识。

2.4 源码实现

2.4.1 Controller

2.4.1.1 AcsDeviceController

AcsDeviceController用来获取门禁设备的唯一标识。

package com.example.platform.controller;

import com.example.platform.constants.Artemis;
import com.example.platform.utils.HttpClientSSLUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 获取门禁设备信息的控制器
 *
 * @Owner:
 * @Time: 2019/3/1-15:45
 */
@RestController
@RequestMapping("/api/resource")
public class AcsDeviceController {
    private static final Logger logger = LoggerFactory.getLogger(AcsDeviceController.class);
    @Autowired
    private HttpClientSSLUtil httpClientSSLUtil;

    private static Map<String, String> acsDeviceAccessingUrl = new HashMap<>();
     static {
        acsDeviceAccessingUrl.put("allAcsDevice", Artemis.ARTEMIS_PATH+"/api/resource/v1/acsDevice/acsDeviceList");
        acsDeviceAccessingUrl.put("acsDeviceList", Artemis.ARTEMIS_PATH+"/api/resource/v1/acsDevice/advance/acsDeviceList");
        acsDeviceAccessingUrl.put("aAcsDeviceInfo", Artemis.ARTEMIS_PATH+"/api/resource/v1/acsDevice/indexCode/acsDeviceInfo");
    }
    public String getAllAcsDeviceUrl() {
         return acsDeviceAccessingUrl.get("allAcsDevice");
    }

    /**
     * @description: 获取门禁设备列表
     * @url:
     * @author: Song Quanheng
     * @date: 2019/3/1-16:30
     * @return:
     */
    @GetMapping("/acsDevice/allAcsDevices")
    @ResponseBody
    public String getAllAcsDevices() {
         logger.info("Enter getAllAcsDevices");
        return httpClientSSLUtil.extractFullResourceList(getAllAcsDeviceUrl());
    }
}

门禁设备信息参见门禁设备信息

2.4.1.2 AcsDoorController

package com.example.platform.controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.platform.constants.Artemis;
import com.example.platform.service.CommonReturn;
import com.example.platform.utils.HttpClientSSLUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.util.*;

/**
 * 获取门禁点相关的信息,可支持按条件查询,全量查询,查询单个门禁点的信息
 *
 * @Owner:
 * @Time: 2019/3/1-16:43
 */
@RestController
@RequestMapping("/api")
public class AcsDoorController {
    private static final Logger LOGGER = LoggerFactory.getLogger(AcsDoorController.class);

    public static final int ALWAYS_OPEN = 0;
    public static final int CLOSE = 1;
    public static final int OPEN = 2;
    public static final int ALWAYS_CLOSE = 3;


    public static final int MAX_DOOR_INDEX_NUM = 10;
    @Autowired
    private HttpClientSSLUtil httpClientSSLUtil;
    private static Map<String, String> acsDoorUrl = new HashMap<>();

    static {
        acsDoorUrl.put("allDoor", Artemis.ARTEMIS_PATH+"/api/resource/v1/acsDoor/acsDoorList");
        acsDoorUrl.put("doorList", Artemis.ARTEMIS_PATH+"/api/resource/v1/acsDoor/advance/acsDoorList");
        acsDoorUrl.put("aDoorInfo", Artemis.ARTEMIS_PATH+"/api/resource/v1/acsDoor/indexCode/acsDoorInfo");
        acsDoorUrl.put("doorControl", Artemis.ARTEMIS_PATH+"/api/acs/v1/door/doControl");
    }

    public String getAllDoorUrl() {
        return acsDoorUrl.get("allDoor");
    }

    public String getDoorControlUrl() {
        return acsDoorUrl.get("doorControl");
    }
    @GetMapping("/resource/acsDoor/allAcsDoors")
    public String getAllDoors() {
        LOGGER.info("Enter getAllDoors");
        return httpClientSSLUtil.extractFullResourceList(getAllDoorUrl());
    }

    @PostMapping(value = "/acs/door/doControl", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public String controlDoor(@RequestBody JSONObject controlInfo) {
        if (hasValidControlInfo(controlInfo)) {
            LOGGER.debug("Enter controlDoor");
            return CommonReturn.httpReturnFailure("输入的反控信息不正确");
        }
        httpClientSSLUtil.setHttpsUrl(getDoorControlUrl());
        return httpClientSSLUtil.doPostString(controlInfo.toJSONString());
    }

    private boolean hasValidControlInfo(JSONObject controlInfo) {
        if (!controlInfo.containsKey("doorIndexCodes") || !controlInfo.containsKey("controlType")) {
            return false;
        }
        JSONArray doorIndexCodes = controlInfo.getJSONArray("doorIndexCodes");
        if (doorIndexCodes.size()>MAX_DOOR_INDEX_NUM) {
            LOGGER.info("doorIndexCodes.size()>MAX_DOOR_INDEX_NUM");
            return false;
        }
        int controlType = controlInfo.getIntValue("controlType");
        Set<Integer> doorControlType = new HashSet<>();
        Collections.addAll(doorControlType, ALWAYS_OPEN, CLOSE, OPEN, ALWAYS_CLOSE);
        if (doorControlType.contains(controlType)) {
            LOGGER.debug("unsupported controlType: "+controlType);
            return false;
        }
        return true;
    }
}

2.4.2 Model

 为了编程的方便,在代码中为门禁点和门禁设备建模为类型,方便获取某个特别属性。比如说设备唯一标识,门禁点唯一标识。

2.4.2.1 AcsDevice

package com.example.platform.domain.dto;

import com.alibaba.fastjson.JSONObject;

/**
 * 门禁设备属性说明
 *
 * @Owner: SongQuanHeng
 * @Time: 2019/3/28-15:56
 * @Version:
 * @Change: 新增,为门禁点到门禁设备的映射新增类型。
 */
public class AcsDevice {
    /**
     * 门禁设备唯一标识
     */
    private String acsDevIndexCode;
    private String acsDevName;
    private String acsDevTypeDesc;
    private String acsDevTypeCode;
    private String acsDevTypeName;
    /**
     * 门禁设备Ip和端口
     */
    private String acsDevIp;
    private String acsDevPort;
    private String acsDevCode;
    private String regionIndexCode;
    private String treatyType;
    private String createTime;
    private String updateTme;


    public AcsDevice(JSONObject devInfo) {
        this.acsDevIndexCode = devInfo.getString("acsDevIndexCode");
        this.acsDevIp = devInfo.getString("acsDevIp");
        this.acsDevPort = devInfo.getString("acsDevPort");
    }

    public String getAcsDevIndexCode() {
        return acsDevIndexCode;
    }

    public String getAcsDevIp() {
        return acsDevIp;
    }

    public String getAcsDevPort() {
        return acsDevPort;
    }
}

2.4.2.2 AcsDoor

门禁点,在为了建立映射的过程中,只需要关注门禁点唯一标识和门禁设备唯一标识,这样在建立映射关系时,可以根据门禁设备唯一标识获取该设备的基本信息。

package com.example.platform.domain.dto;

import com.alibaba.fastjson.JSONObject;

/**
 * 门禁点建模为类
 *
 * @Owner: SongQuanHeng
 * @Time: 2019/3/28-17:03
 * @Version:
 * @Change: 把门禁点建模为类
 */
public class AcsDoor {
    /**
     * 门禁点唯一标识
     */
    private String doorIndexCode;
    /**
     * 所属门禁设备唯一标识
     */
    private String acsDevIndexCode;

    public AcsDoor(JSONObject doorInfo) {
        this.doorIndexCode = doorInfo.getString("doorIndexCode");
        this.acsDevIndexCode = doorInfo.getString("acsDevIndexCode");
    }

    public String getDoorIndexCode() {
        return doorIndexCode;
    }

    public void setDoorIndexCode(String doorIndexCode) {
        this.doorIndexCode = doorIndexCode;
    }

    public String getAcsDevIndexCode() {
        return acsDevIndexCode;
    }

    public void setAcsDevIndexCode(String acsDevIndexCode) {
        this.acsDevIndexCode = acsDevIndexCode;
    }
}

2.4.2.3 DoorMappingAcsDevice

DoorMappingAcsDevice类对应的bean的静态成员doorMapDevs负责保存映射关系。

package com.example.platform.constants;

import com.example.platform.domain.dto.AcsDevice;
import org.springframework.stereotype.Component;

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

/**
 * 该类负责门禁点映射成门禁设备
 *
 * @Owner: SongQuanHeng
 * @Time: 2019/3/28-16:18
 * @Version:
 * @Change:
 */
@Component
public class DoorMappingAcsDevice {

    private static Map<String, AcsDevice> doorMapDevs = new HashMap<>();
    public AcsDevice getAcsDevice(String doorIndexCode) {
        return doorMapDevs.get(doorIndexCode);
    }

    public void addAcsDevice(String doorIndexCode, AcsDevice device) {
        doorMapDevs.put(doorIndexCode, device);
    }

    public static Map<String, AcsDevice> getDoorMapDevs() {
        return Collections.unmodifiableMap(doorMapDevs);
    }
}

2.4.3 CommandLiner

 该接口负责调用所有的Controller从服务器里获取所有门禁点信息,所有门禁设备信息,进而构建DoorMappingAvsDevice的运行时实例,即从门禁点唯一标识到门禁设备信息的映射。

package com.example.platform.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.platform.constants.DoorMappingAcsDevice;
import com.example.platform.controller.AcsDeviceController;
import com.example.platform.controller.AcsDoorController;
import com.example.platform.domain.dto.AcsDevice;
import com.example.platform.domain.dto.AcsDoor;
import com.example.platform.utils.ReturnResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.stereotype.Component;

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

/**
 * 在所有Bean生成之后初始化资源
 * 通过在把该InitializeResource初始化资源类使用@Component注解,令该类成为容器管理的bean。
 * @Owner: SongQuanHeng
 * @Time: 2019/3/28-16:38
 * @Version:
 * @Change:
 */
@Component
public class InitializeResource implements CommandLineRunner {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    //获取所有门禁设备
    @Autowired
    private AcsDeviceController acsDeviceController;

    //获取所有门禁点
    @Autowired
    private AcsDoorController acsDoorController;

    @Autowired
    private DoorMappingAcsDevice doorMappingAcsDevice;

    @Override
    public void run(String... args) throws Exception {
        logger.debug("Enter run");
        Map<String, AcsDevice> devMap = new HashMap<>();
        ReturnResult resDevs = new ReturnResult(acsDeviceController.getAllAcsDevices());
        if (!resDevs.isRequestSuccessful()) {
            logger.debug("acsDeviceController.getAllAcsDevices() fails");
            throw new Exception("acsDeviceController.getAllAcsDevices() fails");
        }
        JSONArray allDevs = resDevs.getResourceList();
        for (int i = 0; i < allDevs.size(); i++) {
            AcsDevice device = new AcsDevice(allDevs.getJSONObject(i));
            devMap.put(device.getAcsDevIndexCode(), device);
        }
        ReturnResult resDoors = new ReturnResult(acsDoorController.getAllDoors());
        if (!resDevs.isRequestSuccessful()) {
            logger.debug("acsDoorController.getAllDoors() fails");
            throw new Exception("acsDoorController.getAllDoors() fails");
        }
        JSONArray allDoors = resDoors.getResourceList();

        for (int i = 0; i < allDoors.size(); i++) {
            JSONObject doorInfo = allDoors.getJSONObject(i);
            AcsDoor door = new AcsDoor(allDoors.getJSONObject(i));

            String doorAcsDevIndexCode = door.getAcsDevIndexCode();
            if (!devMap.containsKey(doorAcsDevIndexCode)) {
                logger.debug("Impossible situation");
            } else {
                AcsDevice device = devMap.get(doorAcsDevIndexCode);
                doorMappingAcsDevice.addAcsDevice(door.getDoorIndexCode(), device);
            }
        }
        System.out.println("The Runner start to initialize");
    }
}

 在此简要的介绍依赖注入的概念。所谓依赖注入指的是容器负责创建对象和维护对象之间的关系,而不是通过对象本身负责自己的创建和解决自己的依赖。依赖注入的主要目的是为了解耦。体现了一种”组合”的理念。
Spring IoC容器(Application Context)负责创建Bean,并通过容器将功能类Bean注入到你需要的Bean中,Spring提供使用XML,注解,Java配置、groovy配置实现Bean的创建和注入。
通过上述InitializeResource可以看到,我们可以为一个Bean类注入Controller实例,并且调用Controller的REST接口以获取数据,然后把这些获取的数据放入了DoorMappingAcsDevice的静态变量中。

3 引用

CommandLineRunner或者ApplicationRunner接口

CommandLineRunner与ApplicationRunner的使用及源码解析

Spring Boot 启动加载数据CommandLineRunner

4 总结

 文档主要介绍了CommandLineRunner接口的应用场景、并根据一个实例演示了使用CommandLineRunner来进行项目启动之后的资源初始化,并通过在@Component注入@Controller领会Spring依赖注入的基本思想。希望能够通过此文档帮助使用Spring Boot开发的程序员可以灵活的使用CommandLineRunner来完成项目自启动,资源初始化类似的工作需求。

你可能感兴趣的:(类介绍,Spring,Boot)