当我们离开世界的时候,那些人,知道我们来过就好.
上一章简单介绍了SpringBoot通过Cors解决跨域问题(三十一),如果没有看过,请观看上一章
本章节参考 江南一点雨大神的文章: Spring Boot2 系列教程(十五)定义系统启动任务的两种方式
一. 系统启动时执行定时任务
一.一 服务启动后执行定时任务
在系统启动时,我们常常需要初始化一些数据,如构造出必要的数据,将数据库中的某些数据提前写入到缓存里面,获取当前服务器的相关信息等。
@SpringBootApplication
public class InitApplication {
public static void main(String[] args) {
SpringApplication.run(InitApplication.class,args);
System.out.println(">>>>>>>项目启动成功");
//执行初始化数据.
initData();
}
public static void initData(){
System.out.println("静态方法初始化数据");
}
}
这样的操作, initData() 并不是系统启动时执行的定时任务,而是系统启动后执行的.
这样写,当不牵扯到组件 @Component 相关的注解方法时,也就是说只有简单的方法处理时,虽然可以进行初始化数据,但不规范,一般不采用这种方式处理.
本章节代码层次结构如下:
定义一个接口和一个实现
UserService 接口
public interface UserService {
public void set();
public void get();
}
UserServiceImpl 实现
@Service
public class UserServiceImpl implements UserService {
@Override
public void set() {
System.out.println("UserService--->set值");
}
@Override
public void get() {
System.out.println("UserService--->取出值");
}
}
一.二 Listener 监听器执行定时任务
在 JSP /Servlet 时,我们常常使用 Listener 监听器来初始化数据.
SpringBoot 也支持这种形式.
一.二.一 事件监听器 InitListener
在 listener 包下创建事件监听器
package top.yueshushu.init.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* @ClassName:InitListener
* @Description 监听器
* @Author zk_yjl
* @Date 2021/11/30 9:00
* @Version 1.0
* @Since 1.0
**/
public class InitListener implements ServletContextListener {
/**
容器启动时
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
//初始化必要的数据. 常于构建缓存,初始化数据.
System.out.println(">>>>Listener,容器启动,初始化必要的数据");
}
/**
容器突然销毁时
*/
@Override
public void contextDestroyed(ServletContextEvent sce) {
//容器关闭了,进行数据备份的处理.
System.out.println(">>>>Listener,容器销毁,将重要数据备份");
}
}
一.二.二 启动添加 Listener
@SpringBootApplication
public class InitApplication {
public static void main(String[] args) {
SpringApplication.run(InitApplication.class,args);
System.out.println(">>>>>>>项目启动成功");
//执行初始化数据.
// initData();
}
/* public static void initData(){
System.out.println("静态方法初始化数据");
}
*/
/**
* 添加Listener
* @date 2021/11/30 9:03
* @author zk_yjl
* @return
*/
@Bean
public ServletListenerRegistrationBean<InitListener> initListenerServletListenerRegistrationBean(){
ServletListenerRegistrationBean<InitListener> registrationBean=new ServletListenerRegistrationBean<>(
new InitListener()
);
return registrationBean;
}
}
在项目启动之前执行了,初始化 ExecutorService 之前.
二. SpringBoot 系统启动任务
通常使用 CommandLineRunner , ApplicationRunner , 和 @PostConstruct三种方式进行处理
关于参数的使用,可以参考江南一点雨大神的这篇文章.
二.一 CommandLineRunner 方式
如启动时,获取机器码
二.一.一 获取机器码的工具类 HardwareUtil
package top.yueshushu.init.util;
import lombok.extern.log4j.Log4j2;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Scanner;
@Log4j2
public class HardwareUtil {
private static final String[] getCpuIdWindows = {"wmic", "cpu", "get", "ProcessorId"};
private static String cpuId;
public static String getCpuId() {
try{
String osName = getOsName();
String cpuId = null;
if (osName.toLowerCase().contains("linux")) {
cpuId = getSerialNumber("dmidecode -t processor | grep 'ID'", "ID", ":");
} else if (osName.toLowerCase().contains("windows")) {
cpuId = getWindowsCpuId();
}
return !StringUtils.hasText(cpuId)?"1020304050607080":cpuId;
}catch (Exception e){
log.error("生成CpuId错误",e);
return "1020304050607080";
}
}
private static String getWindowsCpuId() {
try {
Process process = Runtime.getRuntime().exec(getCpuIdWindows);
process.getOutputStream().close();
Scanner sc = new Scanner(process.getInputStream());
String property = sc.next();
String serial = sc.next();
log.info(property + ": " + serial);
return serial;
} catch (IOException e) {
// TODO Auto-generated catch block
log.error("发生异常", e);
}
return null;
}
public static String getMac() {
NetworkInterface networkInterface = getRealNetworkInterface();
try {
if (networkInterface == null) {
return null;
}
byte[] mac = networkInterface.getHardwareAddress();
return buildMac(mac);
} catch (SocketException e) {
log.error("发生异常{}", e);
}
return null;
}
public static String getServerId() {
return buildServerId(getIpAddress());
}
private static String getIpAddress() {
NetworkInterface networkInterface = HardwareUtil.getRealNetworkInterface();
return networkInterface.getInterfaceAddresses().get(0).getAddress().getHostAddress();
}
private static NetworkInterface getRealNetworkInterface() {
try {
//枚举本机所有的网络接口
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()) {
NetworkInterface netInterface = allNetInterfaces.nextElement();
// 去除回环接口,子接口,未运行和接口
if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) {
continue;
}
return netInterface;
}
} catch (SocketException e) {
System.err.println("Error when getting host ip address" + e.getMessage());
}
return null;
}
private static String buildServerId(String address) {
List<String> ips = Arrays.asList(address.split("\\."));
return ips.stream().mapToInt(Integer::parseInt).boxed().map(Object::toString).reduce("", String::concat);
}
private static String buildMac(byte[] mac) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < mac.length; i++) {
if (i != 0) {
sb.append("-");
}
String s = Integer.toHexString(mac[i] & 0xFF);
sb.append(s.length() == 1 ? 0 + s : s);
}
return sb.toString().toUpperCase();
}
private static String getOsName() {
return System.getProperty("os.name");
}
private static String getSerialNumber(String cmd, String record, String symbol) {
String execResult = executeLinuxCmd(cmd);
String[] infos = execResult.split("\n");
for (String info : infos) {
info = info.trim();
if (info.contains(record)) {
info.replace(" ", "");
String[] sn = info.split(symbol);
return sn[1];
}
}
return null;
}
private static String executeLinuxCmd(String cmd) {
try {
log.debug("got cmd job : " + cmd);
Runtime run = Runtime.getRuntime();
Process process;
process = run.exec(cmd);
InputStream in = process.getInputStream();
// BufferedReader bs = new BufferedReader(new InputStreamReader(in));
StringBuffer out = new StringBuffer();
byte[] b = new byte[8192];
for (int n; (n = in.read(b)) != -1; ) {
out.append(new String(b, 0, n));
}
in.close();
process.destroy();
return out.toString();
} catch (Exception e) {
log.error("发生异常", e);
}
return null;
}
}
二.一.二 实现 CommandLineRunner 的 run 方法
package top.yueshushu.init.line;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
import top.yueshushu.init.util.HardwareUtil;
@Component
@Order(1)
public class InitCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) {
//args 获取传入的参数。
//容器启动时,进行的操作。 如获取计算机的 机器码
String cpuId = HardwareUtil.getCpuId();
if(!StringUtils.hasText(cpuId)){
System.out.println(">>>没有机器码");
}
String singleSignature = Base64Utils.encodeToString(cpuId.getBytes());
System.out.println(">>>机器码是:"+singleSignature);
}
}
@Order(1) 表示顺序。 可以定义多个启动方法, 数据越小,越先启动。
参数 String … args 对应的就是 main 方法里面的参数,获取参数的方式也是相同的.
二.一.三 测试
是在服务启动之后,进行处理的. 这个时候,已经是拥有 @Component, @Controller,@Service,@Repository 相关的实例了
二.一.四 使用组件
@Component
@Order(2)
public class InitCommandLineRunner2 implements CommandLineRunner {
@Autowired
private UserService userService;
@Override
public void run(String... args) {
userService.set();
userService.get();
}
}
启动服务
二.二 ApplicationRunner
将 InitCommandLineRunner 和 InitCommandLineRunner2 去掉 @Order 和 @Component 注解
二.二.一 实现 ApplicationRunner 的 run 接口
@Component
@Order(3)
public class InitApplicationRunner implements ApplicationRunner {
@Autowired
private UserService userService;
@Override
public void run(ApplicationArguments args) throws Exception {
//在运行时,进行的操作.
Random random=new Random();
System.out.println(">>>输出值:"+random.nextInt(100));
userService.set();
userService.get();
}
}
参数使用的是 ApplicationArguments args
- args.getNonOptionArgs();`可以用来获取命令行中的无key参数(和CommandLineRunner一样)
- args.getOptionNames();`可以用来获取所有key/value形式的参数的key
-
args.getOptionValues(key);
可以根据key获取key/value 形式的参数的value -
args.getSourceArgs();
则表示获取命令行中的所有参数
二.二.二 测试
二.三 @PostConstruct 注解
@PostConstruct 注解,用于在依赖关系注入完成之后,需要执行的方法,用于执行初始化,只执行一次。
也就是说, 是在 @Autowired/@Resource 之后执行的注解
二.三.一 不需要继承接口
@Component
@Order(4)
public class InitMethod {
@Autowired
private UserService userService;
@PostConstruct
public void initName(){
System.out.println(">>>>初始化名称模块");
}
@PostConstruct
public void initSet(){
userService.set();
}
@PostConstruct
public void initGet(){
userService.get();
}
}
二.三.二 测试
在 ExecutorService 服务之前执行的。
与 init() 方法相同.
二.三.三 定义静态类
在实际项目中,一个相对完整的业务链,会有很多个 相关的 Service 。
通常会将相应的几个 Service进行封装,封装成一个工具类,对外提供方法.
先将 InitMethod 类上的 @Component 注解去掉
二.三.三.一 不使用 @PostConstructor 注解
- 定义工具类
@Component
public class MyUserServiceUtil {
@Autowired
private UserService userService;
public void handler(){
//先设置值
userService.set();
//再获取值
userService.get();
}
}
- 使用工具类
先注入,再使用
@SpringBootTest
public class PostTest {
//先注入
@Autowired
private MyUserServiceUtil myUserServiceUtil;
@Test
public void test(){
System.out.println(">>>先注入,再使用");
//再使用
myUserServiceUtil.handler();
}
}
每次都需要先注入,再使用。
如果能够定义成相关的静态方法,就好了.
二.三.三.二 使用 @PostConstructor 注解处理成静态类
@Component
public class MyUserServiceUtil2 {
//执行的顺序,先 Component, 再Autowired,后 PostConstruct
//内部注入
@Autowired
private UserService userService;
//定义代理的静态属性
private static UserService userServiceImpl;
@PostConstruct
public void setComponent(){
//赋予属性值
userServiceImpl=userService;
}
//方法是静态的,那么 调用者必须是静态的.
public static void handler(){
//先设置值
userServiceImpl.set();
//再获取值
userServiceImpl.get();
}
}
在使用时,直接用静态方法的形式即可.
@SpringBootTest
public class PostTest {
//先注入
@Autowired
private MyUserServiceUtil myUserServiceUtil;
@Test
public void test(){
System.out.println(">>>先注入,再使用");
//再使用
myUserServiceUtil.handler();
}
@Test
public void test2(){
System.out.println("静态方法,直接使用");
MyUserServiceUtil2.handler();
}
}
运行 test2() 方法
这种形式,在开发中很常用,一定要记住.
这就是SpringBoot 系统启动任务的常用方法.
本章节的代码放置在 github 上:
https://github.com/yuejianli/springboot/tree/develop/SpringBoot_Init
谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!