springboot3.0自动配置

目标

本文主要介绍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
     */
    @Override
    default 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;

    @Override
    public 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;
    }

    @Override
    public void quit() {
        checkInit();
        driver.quit();
        bInit = false;
    }

    @Override
    public void executeScript(String script) {
        checkInit();
        js.executeScript(script);
    }

    @Override
    public WebElement findByExecuteScript(String script) {
        checkInit();
        return (WebElement) js.executeScript(script);
    }

    @Override
    public String getStrByExecuteScript(WebElement webElement, String script) {
        return (String) js.executeScript(script, webElement);
    }

    @Override
    public List<WebElement> findByCssSelector(String selector) {
        checkInit();
        List<WebElement> webElementList = driver.findElements(By.cssSelector(selector));
        return webElementList;
    }

    @Override
    public List<WebElement> findByCssSelector(WebElement webElement, String selector) {
        // todo 为啥这里不用 checkInit?
        return webElement.findElements(By.cssSelector(selector));
    }

    @Override
    public List<WebElement> findByIdSelector(String selector) {
        checkInit();
        List<WebElement> webElementList = driver.findElements(By.id(selector));
        return webElementList;
    }

    @Override
    public List<WebElement> findByIdSelector(WebElement webElement, String selector) {
        // todo 为啥这里不用 checkInit?
        return webElement.findElements(By.id(selector));
    }

    @Override
    public List<WebElement> findByTag(String tag) {
        checkInit();
        List<WebElement> webElementList = driver.findElements(By.tagName(tag));
        return webElementList;
    }

    @SneakyThrows
    @Override
    public void openUrl(String url) {
        checkInit();
        // 打开网页
        driver.get(url);
        while (!isOpenComplete()) {
            // 页面还没有打开,等待 1 秒
            Thread.sleep(1000);
        }
        log.debug("open {} success", url);
    }

    @Override
    public boolean isOpenComplete() {
        // 使用 JavaScript 检查页面加载状态,确保网页打开之后才执行后续的操作
        String readyState = (String) js.executeScript("return document.readyState");
        return "complete".equals(readyState);
    }

    @Override
    public 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的源码更好,我的代码也就我自己的项目用用,写的对不对也不知道呢

posted @   hellozjf  阅读(130)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示