问题
线上问题遇到一个接口第一次访问特别慢的问题,后来说是因为该接口加了某注解,所以第一次请求比较慢,初步解决办法就是启动后先请求一次就好了。
代码
模拟测试接口
@RequestMapping("/hello") public String hello() { return LocalDateTime.now().toString(); }
核心接口CommandLineRunner
package com.example.autorequest; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; /** * @author chaird * @create 2020-07-31 22:43 */ @Component @Slf4j public class StartAutoRequestService implements CommandLineRunner { @Override public void run(String... args) throws Exception { String httpUrl = "http://localhost:8082/hello"; // 链接 HttpURLConnection connection = null; InputStream is = null; BufferedReader br = null; StringBuffer result = new StringBuffer(); try { // 创建连接 URL url = new URL(httpUrl); connection = (HttpURLConnection) url.openConnection(); // 设置请求方式 connection.setRequestMethod("GET"); // 设置连接超时时间 connection.setReadTimeout(15000); // 开始连接 connection.connect(); // 获取响应数据 if (connection.getResponseCode() == 200) { // 获取返回的数据 is = connection.getInputStream(); if (null != is) { br = new BufferedReader(new InputStreamReader(is, "UTF-8")); String temp = null; while (null != (temp = br.readLine())) { result.append(temp); } } } } catch (IOException e) { e.printStackTrace(); } finally { if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } // 关闭远程连接 connection.disconnect(); } System.out.println(result.toString()); } }
原理
首先在自己实现CommandLineRunner接口的类的run方法里打个断点,看一下调用栈,这样好知道在哪打断点
在上图箭头处的方法的第一行打断点
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); //断点走到这,此时控制台已经打印出Tomcat started on port(s): 8082 (http) with context path '' //断点走到这,此时控制台已经打印出Tomcat started on port(s): 8082 (http) with context path '' //断点走到这,此时控制台已经打印出Tomcat started on port(s): 8082 (http) with context path '' afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); //开始调用实现CommandLineRunner接口的方法 //开始调用实现CommandLineRunner接口的方法 //开始调用实现CommandLineRunner接口的方法 callRunners(context, applicationArguments); } catch (Throwable ex) { } try { listeners.running(context); } catch (Throwable ex) { } return context; }
进入上图的callRunners方法
private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); //拿到实现CommandLineRunner接口的实现类 runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { //调用方法,此时在跟进去就进入自己写的方法里了 callRunner((CommandLineRunner) runner, args); } } }