20210518_J2EE容器SPI查找机制学习笔记3
1概述
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。
Java提供的一套用来被第三方实现或者扩展的API(JDK ServiceLoader),它可以用来启用框架扩展和替换组件。
Java SPI 实际上是 “基于接口的编程+策略模式+配置文件” 组合实现的动态加载机制。
本文主要基于Tomcat容器结合代码进行ServletContainerInitializer(简称SCI)机制的分析。
1.1原理分析
tomcat容器在启动时,会去扫描当前classpath类路径下的所有jar包。META-INF/services下的文件,文件特定接口名称为javax.servlet.ServletContainerInitializer,文件内容为该接口的实现类。
该实现类作用:
- 定义了注解中自己感兴趣的HandlesTypes(接口XXX)。
- tomcat启动时,调用了ServiceLoader对象中的api方法(基于Jdk 默认spi机制),并且内置自动执行SCI接口默认onStartup方法,找到所有实现该接口XXX的所有实现类,并反射创建对象,缓存到Set集合中。
- 执行用户后续onStartup操作。
1.2web.xml配置回顾
tomcat下的conf中也有一个web.xml文件,没错的,所有的JavaWeb项目中web.xml都继承自服务器下的web.xml。
1.1.1配置schema
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
1.1.2传统servlet配置
创建ContextLoaderListener对象,扫描service、dao,并根据配置初始化。
<!--5.begin_传统servlet配置-->
<servlet>
<servlet-name>myindexServletName</servlet-name>
<servlet-class>com.kikop.mytraditionspringmvc.myservlert.IndexServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myindexServletName</servlet-name>
<url-pattern>/index.do</url-pattern>
</servlet-mapping>
<!--end_传统servlet配置-->
2代码实战
2.1启动原生tomcat
2.1.1maven配置
<!--1.引入内嵌 tomcat-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.17</version>
</dependency>
<!--2.tomcat-embed-jasper-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.17</version>
</dependency>
2.1.2测试
package com.kikop.mytraditionspringmvc.Test;
import com.kikop.mytraditionspringmvc.myservlert.IndexServlet;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
/**
* @author kikop
* @version 1.0
* @project Name: mytomcatspidemo
* @file Name: MyNativeTomcatTest
* @desc MyNativeServletTomcatTest
* @date 2021/5/19
* @time 10:50
* @by IDE: IntelliJ IDEA
*/
public class MyNativeServletTomcatTest {
// 1.JDK:ServiceLoader
// 2.SpringMvc
// 默认处理 WebApplicationInitializer 类型
// 扩展点实现:SpringServletContainerInitializer impl -->扩展点:javax.servlet.ServletContainerInitializer
/**
* 启动原生tomcat
* http://localhost:8088/tomcatspi/index.do
*/
private static void startNativeTomcat() throws LifecycleException {
// 创建内嵌tomcat
Tomcat tomcat = new Tomcat();
// 1.配置connector
Connector connector = new Connector();
connector.setPort(8088);
connector.setURIEncoding("UTF-8");
tomcat.getService().addConnector(connector);
// 2.关联Servlet
String contextPath = "/tomcatspi";
String myindexServletName = "myindexServletName";
// 2.1.普通 servlet注册tomcat(3种方式)
// 1.web.xml
// 2.注解 @WebServlet,加在类:IndexServlet上
// 3.api
IndexServlet indexServlet = new IndexServlet();
// 2.2.配置上下文
Context context = tomcat.addContext(contextPath, null);
tomcat.addServlet(context, myindexServletName, indexServlet);
// 2.3.配置servletMapping
context.addServletMappingDecoded("/index.do", myindexServletName);
// 3.启动
tomcat.start();
// 4.并阻塞
tomcat.getServer().await();
}
public static void main(String[] args) throws LifecycleException {
startNativeTomcat();
}
}
2.2启动web tomcat spi
2.2.1定义感兴趣的接口MyWebApplicationInitializer
package com.kikop.mytraditionspringmvc.mycontainer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
public interface MyWebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
2.2.2定义实现类
package com.kikop.mytraditionspringmvc.mycontainer.impl;
import com.kikop.mytraditionspringmvc.mycontainer.MyWebApplicationInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
/**
* @author kikop
* @version 1.0
* @project Name: mytomcatspidemo
* @file Name: MyCustomAppInitializer
* @desc MyCustomAppInitializer
* @date 2021/5/19
* @time 10:50
* @by IDE: IntelliJ IDEA
*/
public class MyCustomAppInitializer implements MyWebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("【MyCustomAppInitializer】自定义扩展服务");
}
}
2.2.3定义SPI接口实现类
package com.kikop.mytraditionspringmvc.mechanism;
import com.kikop.mytraditionspringmvc.mycontainer.MyWebApplicationInitializer;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author kikop
* @version 1.0
* @project Name: mytomcatspidemo
* @file Name: MyCustomAppInitializer
* @desc WebApplicationInitializer类采集入口
* 测试时,需将 META-INF.notservices--> META-INF.services
* @date 2021/5/19
* @time 10:50
* @by IDE: IntelliJ IDEA
*/
@HandlesTypes(MyWebApplicationInitializer.class)
public class MyTotalWebInitializer implements ServletContainerInitializer {
public void onStartup(Set<Class<?>> myWebAppInitializerClasses, ServletContext servletContext) throws ServletException {
System.out.println("【MyTotalWebInitializer】开始执行 tocmat spi查找机制");
// 1.收集感兴趣的类
for (Class<?> gatherClazz : myWebAppInitializerClasses) {
System.out.println(gatherClazz.toString());
}
// 2.创建对象列表XXX
// 3.执行对象列表XXXonStartup
}
}
2.2.4测试
package com.kikop.mytraditionspringmvc.Test;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
/**
* @author kikop
* @version 1.0
* @project Name: mytomcatspidemo
* @file Name: MyNativeTomcatTest
* @desc MyWebAppTomcatTest
* @date 2021/5/19
* @time 10:50
* @by IDE: IntelliJ IDEA
*/
public class MyWebAppTomcatTest {
/**
* 启动原生tomcat
* 需声明项目为 webapp项目,否则不会执行SPI查找机制
* http://localhost:8088/tomcatspi/index.do
*/
private static void startTomcatWebSupport() throws LifecycleException {
Tomcat tomcat = new Tomcat();
// 1.配置connector
Connector connector = new Connector();
connector.setPort(8088);
connector.setURIEncoding("UTF-8");
tomcat.getService().addConnector(connector);
String contextPath = "/mytomcatspi";
// 2.注意点:
// 必须需声明项目为 webapp项目,否则tomcat.start不会执行SPI查找机制
// 等同于配置了web.xml
tomcat.addWebapp(contextPath,
"D:\\workdirectory\\mapexperiment\\mytomcatspi\\");
// 3.启动
tomcat.start();
// 4.并阻塞
tomcat.getServer().await();
}
public static void main(String[] args) throws LifecycleException {
startTomcatWebSupport();
}
}
参考
1java SPI机制
https://blog.csdn.net/gaohaicheng123/article/details/105824988
2java SPI 机制 在Tomcat,spring-mvc启动及servlet3.0中的应用
https://blog.csdn.net/gaohaicheng123/article/details/105827295
3Features:Spring MVC and Spring WebFlux web frameworks
https://spring.io/projects/spring-framework
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html
网友评论