目标
本文主要介绍springboot3.0是如何创建一个可以进行自动配置的jar包的
自动配置的定义是,一个jar包里面定义了一些spring的bean,当导入这个jar包的时候会自动将这些bean导入进去
方法
创建 AutoConfiguration.imports 文件
创建目录 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
在该文件里面写入自动配置类的全限域名,例如:cn.hellozjf.game.pcr.common.CommonAutoConfiguration
创建自动配置类
代码内容如下:
package cn.hellozjf.game.pcr.common;import cn.hellozjf.game.pcr.common.config.WebDriverAutoConfiguration;
import cn.hellozjf.game.pcr.common.config.WebDriverConfig;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;@Import({WebDriverAutoConfiguration.class})
@EnableConfigurationProperties({WebDriverConfig.class})
public class CommonAutoConfiguration {}
其中,WebDriverAutoConfiguration 定义了自动配置时会导入的 bean,WebDriverConfig 定义了自动配置时需要读取的配置文件
WebDriverAutoConfiguration
这个类里面定义了各种需要导入的 bean
package cn.hellozjf.game.pcr.common.config;import cn.hellozjf.game.pcr.common.service.IWebDriverService;
import cn.hellozjf.game.pcr.common.service.WebDriverServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;@AllArgsConstructor
public class WebDriverAutoConfiguration {private final WebDriverConfig webDriverConfig;@Bean@ConditionalOnMissingBean(IWebDriverService.class)public IWebDriverService webDriverService() {return new WebDriverServiceImpl(webDriverConfig);}
}
如上面的代码所示,它会在构造的时候将 WebDriverConfig 配置信息全部导入进来,如果在初始化的时候发现没有定义 IWebDriverService 这个 bean,那么就会实例化这个 bean
WebDriverConfig
它的代码如下所示
package cn.hellozjf.game.pcr.common.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "webdriver")
public class WebDriverConfig {private Boolean headless;private String proxy;private String driverKey;private String driverValue;
}
它对应配置文件中的内容如下
IWebDriverService
这是 bean 的定义基类,代码如下
package cn.hellozjf.game.pcr.common.service;import org.openqa.selenium.WebElement;
import org.springframework.beans.factory.InitializingBean;import java.util.List;/*** web浏览器操作服务*/
public interface IWebDriverService extends InitializingBean {/*** 初始化*/void init();/*** 退出浏览器*/void quit();/*** 执行 JS 脚本* @param script*/void executeScript(String script);/*** 通过执行 JS 脚本,获取网页元素* @param script* @return*/WebElement findByExecuteScript(String script);/*** 从网页元素中,通过 JS 脚本获取该元素里面的字符串数据* @param webElement* @param script* @return*/String getStrByExecuteScript(WebElement webElement, String script);/*** 根据 CSS 选择器查找网页元素* @param selector* @return*/List<WebElement> findByCssSelector(String selector);/*** 在某个网页元素下,根据 CSS 选择器查找网页元素* @param webElement* @param selector* @return*/List<WebElement> findByCssSelector(WebElement webElement, String selector);/*** 根据 ID 选择器查找网页元素* @param selector* @return*/List<WebElement> findByIdSelector(String selector);/*** 在某个网页元素下,根据 ID 选择器查找网页元素* @param webElement* @param selector* @return*/List<WebElement> findByIdSelector(WebElement webElement, String selector);/*** 根据标签查找网页元素* @param tag* @return*/List<WebElement> findByTag(String tag);/*** 打开网页* @param url*/void openUrl(String url);/*** 判断打开页面操作是否完成* @return*/boolean isOpenComplete();/*** 阻塞进程,直到页面元素出现,或者超过timeout秒* @param element* @param timeout*/void waitUntilElementExist(String element, int timeout);/*** 不需要实现 afterPropertiesSet(),我也不知道这有啥用,照着抄就对了* @throws Exception*/@Overridedefault void afterPropertiesSet() throws Exception {}
}
WebDriverServiceImpl
这是 IWebDriverService 的实现类,内容如下
package cn.hellozjf.game.pcr.common.service;import cn.hellozjf.game.pcr.common.config.WebDriverConfig;
import cn.hellozjf.game.pcr.common.util.OSUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.util.StringUtils;import java.time.Duration;
import java.util.List;@Slf4j
@RequiredArgsConstructor
public class WebDriverServiceImpl implements IWebDriverService {private final WebDriverConfig webDriverConfig;private boolean bInit = false;private WebDriver driver;private JavascriptExecutor js;@Overridepublic void init() {if (bInit) {log.error("WebDriverService已经初始化");return;}// 设置 ChromeDriver 的路径System.setProperty(webDriverConfig.getDriverKey(), webDriverConfig.getDriverValue());ChromeOptions options = new ChromeOptions();// 获取 driver 和 js 变量if (StringUtils.hasLength(webDriverConfig.getProxy())) {// 说明需要走代理options.addArguments("--proxy-server=" + webDriverConfig.getProxy());}// 设置无界面if (webDriverConfig.getHeadless()) {options.addArguments("--headless");}if (OSUtil.isLinux()) {options.addArguments("--no-sandbox"); // 适用于Linux环境options.addArguments("--disable-dev-shm-usage"); // 适用于Linux环境}// 创建 WebDriver 实例driver = new ChromeDriver(options);js = (JavascriptExecutor) driver;bInit = true;}@Overridepublic void quit() {checkInit();driver.quit();bInit = false;}@Overridepublic void executeScript(String script) {checkInit();js.executeScript(script);}@Overridepublic WebElement findByExecuteScript(String script) {checkInit();return (WebElement) js.executeScript(script);}@Overridepublic String getStrByExecuteScript(WebElement webElement, String script) {return (String) js.executeScript(script, webElement);}@Overridepublic List<WebElement> findByCssSelector(String selector) {checkInit();List<WebElement> webElementList = driver.findElements(By.cssSelector(selector));return webElementList;}@Overridepublic List<WebElement> findByCssSelector(WebElement webElement, String selector) {// todo 为啥这里不用 checkInit?return webElement.findElements(By.cssSelector(selector));}@Overridepublic List<WebElement> findByIdSelector(String selector) {checkInit();List<WebElement> webElementList = driver.findElements(By.id(selector));return webElementList;}@Overridepublic List<WebElement> findByIdSelector(WebElement webElement, String selector) {// todo 为啥这里不用 checkInit?return webElement.findElements(By.id(selector));}@Overridepublic List<WebElement> findByTag(String tag) {checkInit();List<WebElement> webElementList = driver.findElements(By.tagName(tag));return webElementList;}@SneakyThrows@Overridepublic void openUrl(String url) {checkInit();// 打开网页driver.get(url);while (!isOpenComplete()) {// 页面还没有打开,等待 1 秒Thread.sleep(1000);}log.debug("open {} success", url);}@Overridepublic boolean isOpenComplete() {// 使用 JavaScript 检查页面加载状态,确保网页打开之后才执行后续的操作String readyState = (String) js.executeScript("return document.readyState");return "complete".equals(readyState);}@Overridepublic void waitUntilElementExist(String element, int timeout) {checkInit();WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(timeout));wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(element)));}private void checkInit() {if (!bInit) {throw new RuntimeException("WebDriverService还未初始化");}}
}
流程说明
当我在其他项目中导入了这个 jar
那么这个 jar 包里面的 META-INF/spring/AutoConfiguration.imports 就会被 spring 容器扫描到,然后就回去加载自动配置类CommonAutoConfiguration
因为CommonAutoConfigruation
引用了WebDriverConfig
,所以就会读取application.yml
中的配置
因为CommonAutoConfiguration
导入了WebDriverAutoConfiguration
,所以就会加载WebDriverAutoConfiguration
,并且创建出一个IWebDriverService
的bean,这个bean的内容会根据配置进行初始化
最后其它项目就能获取到这个bean,并进行使用了
IWebDriverService webDriverService = SpringUtil.getBean(IWebDriverService.class);
webDriverService.init();
后话
本文是我在等待gradle下载依赖时候编写,依赖下载太耗时间了,等的我心很烦,所以本文写的也就意思一下
这个自动配置我是参考pigx实现的,有兴趣的读者还是研究pigx的源码更好,我的代码也就我自己的项目用用,写的对不对也不知道呢