(1). 概述

上一小篇,剖析了PlaywrightImpl,它在初始化时,会委托给Driver类,所以,这一小篇是肯定少不了要剖析该类.

(2). Driver.ensureDriverInstalled

public abstract class Driver {
  protected final Map<String, String> env = new LinkedHashMap<>();
  // 单例模式
  private static Driver instance;

  // *****************************************************************
  // 单例模式
  // 1. 创建:Driver
  // *****************************************************************
  public static synchronized Driver ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
    if (instance == null) {
      instance = createAndInstall(env, installBrowsers);
    }
    return instance;
  } // end ensureDriverInstalled
  
  
  public static Driver createAndInstall(Map<String, String> env, Boolean installBrowsers) {
	  try {
		// *****************************************************************
		// 1.1 创建Driver
        // *****************************************************************
		Driver instance = newInstance();
		logMessage("initializing driver");
		// *****************************************************************
		// 2. 初始化
		// *****************************************************************
		instance.initialize(env, installBrowsers);
		logMessage("driver initialized.");
		return instance;
	  } catch (Exception exception) {
		throw new RuntimeException("Failed to create driver", exception);
	  }
  } // end createAndInstall
  
  
  private static Driver newInstance() throws Exception {
	  // *****************************************************************
	  // 1.2 读取系统环境变量,看是否有配置Driver,如果有的话,通过:PreinstalledDriver包裹一层
	  // *****************************************************************
      String pathFromProperty = System.getProperty("playwright.cli.dir");
      if (pathFromProperty != null) { // false
        return new PreinstalledDriver(Paths.get(pathFromProperty));
      }
	  
	  // *****************************************************************
	  // 1.3 读取环境变量:playwright.driver.impl
	  // 如果环境变量没有配置,则默认值为:com.microsoft.playwright.impl.driver.jar.DriverJar
	  // 注意:这个类在另一个工程里:driver-bundle,这个工程很重要,呆会会深入讲解.
	  //  通过反射创建:Driver的实现
	  // *****************************************************************
      String driverImpl = System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.driver.jar.DriverJar");
      Class<?> jarDriver = Class.forName(driverImpl);
      return (Driver) jarDriver.getDeclaredConstructor().newInstance();
  } // end newInstance
  
  
  // *****************************************************************
  // 2.1 初始化
  // *****************************************************************
  private void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
  	this.env.putAll(env);
  	initialize(installBrowsers);
  } // end initialize
  
  // 2.2 initialize方法是一个抽象方法,预留给子类
  protected abstract void initialize(Boolean installBrowsers) throws Exception;
  
}  

(3). DriverJar.initialize

protected void initialize(Boolean installBrowsers) throws Exception {
	// ... ...
	// ****************************************************
	// 2.3 提取Driver到临时目录
	// ****************************************************
	extractDriverToTempDir();
	// ... ...
	if (installBrowsers)
	// ****************************************************
	// 3. 安装浏览器
	// ****************************************************
	  installBrowsers(env);
}

(4). DriverJar.extractDriverToTempDir

// 提取驱动程序到临时目录
void extractDriverToTempDir() throws URISyntaxException, IOException {
    ClassLoader classloader = Thread.currentThread().getContextClassLoader();
	// **********************************************************
	// 2.3.1 从ClassPath中寻找:driver/mac目录,我断点后,发现在这个JAR包里:driver-bundle-1.27.0.jar
	//       所以,能断定,在加载playwright依赖时,会加载driver-bundle-1.27.0.jar,并且这个包有点大.
	// jar:file:/Users/lixin/.m2/repository/com/microsoft/playwright/driver-bundle/1.21.0/driver-bundle-1.21.0.jar!/driver/mac
	// **********************************************************
    URI originalUri = classloader.getResource("driver/" + platformDir()).toURI();
	
	// **********************************************************
	// 2.3.2 解压JAR包里的内容到临时目录
	// **********************************************************
    URI uri = maybeExtractNestedJar(originalUri);
	// ... ...
}

(5). DriverJar.maybeExtractNestedJar

private URI maybeExtractNestedJar(final URI uri) throws URISyntaxException {
    if (!"jar".equals(uri.getScheme())) {
      return uri;
    }
	
    final String JAR_URL_SEPARATOR = "!/";
    String[] parts = uri.toString().split("!/");
    if (parts.length != 3) {
      return uri;
    }
	
    String innerJar = String.join(JAR_URL_SEPARATOR, parts[0], parts[1]);
    URI jarUri = new URI(innerJar);
    try (FileSystem fs = FileSystems.newFileSystem(jarUri, Collections.emptyMap())) {
      Path fromPath = Paths.get(jarUri);
      Path toPath = driverTempDir.resolve(fromPath.getFileName().toString());
	  // ************************************************************
	  // 2.3.3 解压JAR包里的内容到临时目录
	  // ************************************************************
      Files.copy(fromPath, toPath);
      toPath.toFile().deleteOnExit();
      return new URI("jar:" + toPath.toUri() + JAR_URL_SEPARATOR + parts[2]);
    } catch (IOException e) {
      throw new RuntimeException("Failed to extract driver's nested .jar from " + jarUri + "; full uri: " + uri, e);
    }
}

(6). 生成的临时目录内容如下

// ******************************************************************
// playwright.sh是重点
// ******************************************************************
lixin-macbook:playwright-java lixin$ tree -L 2
.
├── LICENSE
├── node
├── package
│   ├── README.md
│   ├── ThirdPartyNotices.txt
│   ├── api.json
│   ├── bin
│   ├── browsers.json
│   ├── cli.js
│   ├── index.d.ts
│   ├── index.js
│   ├── index.mjs
│   ├── lib
│   ├── package.json
│   ├── protocol.yml
│   └── types
└── playwright.sh         

(7). DriverJar.installBrowsers

private void installBrowsers(Map<String, String> env) throws IOException, InterruptedException {
    // ... ... 
	// /Users/lixin/Desktop/playwright-java/playwright.sh
    Path driver = driverPath();
    if (!Files.exists(driver)) {
      throw new RuntimeException("Failed to find driver: " + driver);
    }
	
	// ******************************************************************
	// 3.1 调用操作系统,运行shell,这个步骤会要很长时间,会去下载所有浏览器驱动.
	// ******************************************************************
	// /Users/lixin/Desktop/playwright-java/playwright.sh install 
    ProcessBuilder pb = createProcessBuilder();
    pb.command().add("install");
    pb.redirectError(ProcessBuilder.Redirect.INHERIT);
    pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
    Process p = pb.start();
    boolean result = p.waitFor(10, TimeUnit.MINUTES);
    
	// ... ...
}// end 

(8). Mac下安装浏览器后的位置

lixin-macbook:playwright-java lixin$ ll /Users/lixin/Library/Caches/ms-playwright
drwxr-xr-x    4 lixin  staff   128 10  9 23:54 chromium-1024/
drwxr-xr-x    4 lixin  staff   128 10 10 00:39 chromium-1028/
drwxr-xr-x    5 lixin  staff   160 10  9 23:54 ffmpeg-1007/
drwxr-xr-x    4 lixin  staff   128 10  9 23:58 firefox-1350/
drwxr-xr-x    4 lixin  staff   128 10 10 00:42 firefox-1357/
drwxr-xr-x   16 lixin  staff   512 10 10 00:00 webkit-1715/
drwxr-xr-x   16 lixin  staff   512 10 10 00:46 webkit-1724/

(9). 总结

Driver的作用无非不过就是下载所有的Driver,并安装到系统目录下:/Users/lixin/Library/Caches/ms-playwright.