SpringBoot使用CommandLineRunner接口完成资源初始化方式

目录
  • 1 简介
    • 1.1 应用场景
    • 1.2 CommandLineRunner接口
    • 1.3 ApplicationRunner接口
    • 1.4 @Order注解
    • 1.5 CommandLineRunner和ApplicationRunner区别
  • 2 CommandLineRunner完成资源初始化
    • 2.1 背景介绍
    • 2.2 数据类型关系
    • 2.3 实现过程
    • 2.4 源码实现
  • 3 总结

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 <em>run</em> 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.
 * <p>
 * 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 <em>run</em> 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.
 *
 * <p>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).
 *
 * <p><b>NOTE:</b> 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).
 *
 * <p>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}).
 *
 * <p>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.
 *
 * <p>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.
    * <p>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.
    * <ul>
    * <li>if the option is present and has no argument (e.g.: "--foo"), return an empty
    * collection ({@code []})</li>
    * <li>if the option is present and has a single value (e.g. "--foo=bar"), return a
    * collection having one element ({@code ["bar"]})</li>
    * <li>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"]})</li>
    * <li>if the option is not present, return {@code null}</li>
    * </ul>
    * @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();

}

ApplicaitonArguments类型之间的关系如下:

1.5.1 option-argument和non-option arguments

参见命令解释中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.
 *
 * <h3>Working with option arguments</h3>
 * Option arguments must adhere to the exact syntax:
 * <pre class="code">--optName[=optValue]</pre>
 * 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 <em>without spaces</em>
 * by an equals sign ("=").
 *
 * <h4>Valid examples of option arguments</h4>
 * <pre class="code">
 * --foo
 * --foo=bar
 * --foo="bar then baz"
 * --foo=bar,baz,biz</pre>
 *
 * <h4>Invalid examples of option arguments</h4>
 * <pre class="code">
 * -foo
 * --foo bar
 * --foo = bar
 * --foo=bar --foo=baz --foo=biz</pre>
 *
 * <h3>Working with non-option arguments</h3>
 * 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 实现过程

在建立映射的过程中,只需要关注门禁设备的唯一标识,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接口的应用场景、并根据一个实例演示了使用CommandLineRunner来进行项目启动之后的资源初始化,并通过在@Component注入@Controller领会Spring依赖注入的基本思想。

希望能够通过此文档帮助使用Spring Boot开发的程序员可以灵活的使用CommandLineRunner来完成项目自启动,资源初始化类似的工作需求。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • springboot CommandLineRunner接口实现自动任务加载功能

    CommandLineRunner接口可以实现任务的自动加载,当项目启动完后,就会自动去执行CommandLineRunner接口里的run方法,你可以实现多个CommandLineRunner的实例,使用order来控制执行的顺序! /** * 项目启动后自动运行的代码CommandLineRunner */ @Component @Order(1) public class MyStartupRunner1 implements CommandLineRunner { private Log

  • 使用SpringBoot的CommandLineRunner遇到的坑及解决

    目录 使用场景 两个接口的不同 特殊的场景 遇到的坑 填坑 总结 使用场景 再应用程序开发过程中,往往我们需要在容器启动的时候执行一些操作. Spring Boot中提供了CommandLineRunner和ApplicationRunner两个接口来实现这样的需求. 两个接口的不同 参数不同,其他大体相同,可根据实际需求选择合适的接口使用. CommandLineRunner接口中run方法的参数为String数组,ApplicationRunner中run方法的参数为ApplicationA

  • springboot使用CommandLineRunner解决项目启动时初始化资源的操作

    前言: 在我们实际工作中,总会遇到这样需求,在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等. 今天就给大家介绍一个 Spring Boot 神器,专门帮助大家解决项目启动初始化资源操作. 这个神器就是 CommandLineRunner,CommandLineRunner 接口的 Component 会在所有 Spring Beans 都初始化之后,SpringApplication.run() 之前执行,非常适合在应用程序启动之初进行一些数据初始化的工作. 正文

  • SpringBoot中的ApplicationRunner与CommandLineRunner问题

    目录 概述 实现启动加载接口 ApplicationRunner接口的示例 CommandLineRunner接口示例 CommandLineRunner和ApplicationRunner的执行顺序 我们使用@Order注解按顺序执行这四个bean 概述 开发中可能会有这样的场景,需要在容器启动的时候执行一些内容.比如读取配置文件,数据库连接之类的. SpringBoot给我们提供了两个接口来帮助我们实现这种需求. 两个启动加载接口分别是: CommandLineRunner Applicat

  • 使用SpringBoot + Redis 实现接口限流的方式

    目录 配置 限流注解 定制 RedisTemplate Lua 脚本 注解解析 接口测试 全局异常处理 Redis 除了做缓存,还能干很多很多事情:分布式锁.限流.处理请求接口幂等性...太多太多了 配置 首先我们创建一个 Spring Boot 工程,引入 Web 和 Redis 依赖,同时考虑到接口限流一般是通过注解来标记,而注解是通过 AOP 来解析的,所以我们还需要加上 AOP 的依赖,最终的依赖如下: <dependency> <groupId>org.springfra

  • 浅谈SpringBoot资源初始化加载的几种方式

    目录 一.问题 二.资源初始化 一.问题 在平时的业务模块开发过程中,难免会需要做一些全局的任务.缓存.线程等等的初始化工作,那么如何解决这个问题呢?方法有多种,但具体又要怎么选择呢? 二.资源初始化 1.既然要做资源的初始化,那么就需要了解一下springboot启动过程(这里大体说下启动过程,详细:https://www.jb51.net/article/133648.htm) 按照前面的分析,Spring-boot容器启动流程总体可划分为2部分: 执行注解:扫描指定范围下的bean.载入自

  • Springboot打印接口的三种方式分享

    目录 1 aop切面的方式 1.1 实现思路 1.2 代码实现 1.3 功能测试 2 过滤器的方式 3 拦截器的方式 1 aop切面的方式 1.1 实现思路 引入aop依赖 自定义注解 定义切面,采用环绕通知 1.2 代码实现 1)引入依赖 xml <!--aop--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-

  • SpringBoot加载静态资源的方式

    在SpringBoot中加载静态资源和在普通的web应用中不太一样.默认情况下,spring Boot从classpath下一个叫/static(/public,/resources或/META-INF/resources)的文件夹或从ServletContext根目录提供静态内容.下面我们来写个例子看一下就会一目了然了:首先看一下项目的目录结构: 我们在resources下面的templates目录下建一个home.html的文件,完整目录为:src/main/resources/templa

  • 浅谈Springboot实现拦截器的两种方式

    目录 一.拦截器方式 1.配置HandlerInterceptor 2.注册拦截器 3.使用拦截器的坑 二.过滤器方式 1.实现Filter接口 2.使用过滤器需要注意的 实现过滤请求有两种方式: 一种就是用拦截器,一种就是过滤器 拦截器相对来说比较专业,而过滤器虽然不专业但是也能完成基本的拦截请求要求. 一.拦截器方式 1.配置HandlerInterceptor 下面这个也是我们公司项目拦截器的写法,总体来说感觉还不错,我就记录了下来. 利用了一个静态Pattern变量存储不走拦截器的路径,

  • SpringBoot项目鉴权的4种方式小结

    目录 前言 传统AOP 实现 扩展 Interceptor 实现 扩展 ArgumentResolver 扩展 Filter 扩展 小结 文章介绍了spring-boot中实现通用auth的四种方式,包括 传统AOP.拦截器.参数解析器和过滤器,并提供了对应的实例代码,最后简单总结了下他们的执行顺序. 前言 最近一直被无尽的业务需求淹没,没时间喘息,终于接到一个能让我突破代码舒适区的活儿,解决它的过程非常曲折,一度让我怀疑人生,不过收获也很大,代码方面不明显,但感觉自己抹掉了 java.Tomc

  • SpringBoot 注解事务声明式事务的方式

    springboot 对新人来说可能上手比springmvc要快,但是对于各位从springmvc转战到springboot的话,有些地方还需要适应下,尤其是xml配置.我个人是比较喜欢注解➕xml是因为看着方便,查找方便,清晰明了.但是xml完全可以使用注解代替,今天就扒一扒springboot中事务使用注解的玩法. springboot的事务也主要分为两大类,一是xml声明式事务,二是注解事务,注解事务也可以实现类似声明式事务的方法,关于注解声明式事务,目前网上搜索不到合适的资料,所以在这里

  • springboot全局日期格式化的两种方式

    方式一是配置参数 参数配置的方式就是在json序列化的时候,当字段为日期类型的时候的format类型,就相当于在所有日期字段上加了一个注解 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss"),但是每个字段都加注解太麻烦,所以直接使用全局配置来实现 参数配置也分为两种配置 第一种是yml的配置 spring: jackson: #参数意义: #JsonInclude.Include.A

随机推荐