文章目录
1 前言
前一篇我们简单模拟了springboot的壳,但是有很多其功能并没有实现。
场景描述:之前我们使用的web容器为硬编码绑定的tomcat,但是现在我们服务现在想要根据我们的依赖,决定使用tomcat,或者jetty。比如我们依赖添加tomcat,web容器使用tomcat;我添加jetty依赖,我们web容器使用jetty。
2 结构设计
2.1 web容器体系
第一步:web容器体系设计,我们需要在我们自定义springboot中设计一个顶层接口WebServer,两个实现类TomcatWebServer和JettyWebServer。
WebServer接口代码2.1-1如下:
package com.gaogzhen.springboot.server;
/**
* web容器顶层接口
* @author gaogzhen
*/
public interface WebServer {
/**
* 启动容器
*/
public void start();
}
目前我们只是简单模拟,没有关于容器的逻辑业务处理。
TomcatWebServer和JettyWebServer代码2.1-2如下所示
package com.gaogzhen.springboot.server;
/**
* tomcat web server
*/
public class TomcatWebServer implements WebServer{
@Override
public void start() {
System.out.println("tomcat web server");
}
}
package com.gaogzhen.springboot.server;
/**
* jetty web server
*/
public class JettyWebServer implements WebServer{
@Override
public void start() {
System.out.println("jetty web server");
}
}
2.2 spring容器管理Bean
第二步:自动配置容器管理TomcatWebServer和JettyWebServer类型的bean。
自动配置类WebServerAutoConfiguration代码2.2-1如下所示:
package com.gaogzhen.springboot.server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* web server 自动配置类
*/
@Configuration
public class WebServerAutoConfiguration {
@Bean
public TomcatWebServer tomcatWebServer() {
return new TomcatWebServer();
}
@Bean
public JettyWebServer jettyWebServer() {
return new JettyWebServer();
}
}
我们自定义sprigboot需要同时依赖tomcat和jetty,而我们应用服务需要按依赖来配置web server。首先应用服务依赖自定义springboot,那么就需要解决依赖传递问题。
2 maven 依赖传递
我们应用服务只需要一个web server 就可以,即tomcat或者jetty选一个就可以,默认使用tomcat。我们自定义springboot 模块pom.xml做如下配置:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.60</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.2.28.v20190418</version>
<optional>true</optional>
</dependency>
- tomcat本项目生效,会依赖传递到使用我们自定义springboot的项目
- jetty自定义sprinboot模块生效,不会向下传递
关于maven的传递依赖可自行查阅相关文档或者文末参考[2]
3 ConditionalOnClass
3.1 注解相关科普
第三步:依赖配置好了,但是我们上面的自动装配类里面2个bean目前说默认都会被pring容器管理,我们需要实现按需创建,那么就需要借助条件注解@ConditionalOnClass。
@Conditional
是Spring框架中的一个注解,它允许你根据指定的条件来有选择性地注册Bean或配置类。
将@Conditional
添加到Bean或配置类上,只有在满足指定条件的情况下才会注册或加载它。如果条件不满足,Spring将跳过注册或加载该Bean或配置类。
@ConditionalOnClass
是Spring Boot中常用的一个@Conditional
注解,它用于基于特定的类是否存在于classpath中来有条件地加载Bean或配置类。
当某个类存在于classpath中时,该注解修饰的Bean或配置类才会被加载。否则,它将不会被加载。
3.2 自定义注解
自定义@ConditionalOnClass注解,代码3.2-1如下所示:
package com.gaogzhen.springboot.server;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(MyCondition.class)
public @interface ConditionalOnClass {
String value();
}
MyCondition类代码3.2-2如下所示;
package com.gaogzhen.springboot.server;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
/**
*
*/
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取注解@ConditionalOnClass注解value值
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
String className = (String) attributes.get("value");
// 根据是否成功加载返回ture或者false
try {
context.getClassLoader().loadClass(className);
return true;
} catch (ClassNotFoundException e) {
// e.printStackTrace();
return false;
}
}
}
- 当扫描到类上有我们自定义注解@ConditionalOnClass,执行@Conditional指定的MyCondition类中的matches()方法逻辑;
- 通过类加载器判断是否加载成功来决定是否注册该Bean,即有相关依赖注册该bean,没有想过依赖不注册该bean。
修改我们的WebServerAutoConfiguration配置类代码3.2-1如下;
@Configuration
public class WebServerAutoConfiguration {
@Bean
@ConditionalOnClass("org.apache.catalina.startup.Tomcat")
public TomcatWebServer tomcatWebServer() {
return new TomcatWebServer();
}
@Bean
@ConditionalOnClass("org.eclipse.jetty.server.Server")
public JettyWebServer jettyWebServer() {
return new JettyWebServer();
}
}
- org.apache.catalina.startup.Tomcat是tomcat依赖中相关类
- org.eclipse.jetty.server.Server是jetty依赖中相关的类
问题来了?我们的class是否可以指定依赖jar包中其他类呢?
- 当然可以,但是选择尽量见名知意不会和其他依赖重名,即选择核心类
4 服务测试
其他测试代码同上一篇,因为我们没有功能没完善,启动类上需要加上@Import注解引入我们的自动配置类,代码如下:
@SpringBootApplicationG2zh
@Import(WebServerAutoConfiguration.class)
public class MyApplication {
public static void main(String[] args) {
SpringApplicationG2zh.run(MyApplication.class, args);
}
}
user服务引入自定义springboot,就是使用默认的tomcat;
<dependencies>
<dependency>
<groupId>com.gaogzhen</groupId>
<artifactId>springboot</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
启动服务,控制台输出:
tomcat web server
测试jetty服务器,user pom.xml如下配置:
<dependencies>
<dependency>
<groupId>com.gaogzhen</groupId>
<artifactId>springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.2.28.v20190418</version>
</dependency>
</dependencies>
user服务启动测试,结果如下:
jetty web server
结语
如果小伙伴什么问题或者指教,欢迎交流。
❓QQ:806797785
⭐️源代码仓库地址:https://gitee.com/gaogzhen/springboot-custom
参考:
[1]Springboot视频教程[CP/OL].P118-135.
[2]Maven依赖传递,排除依赖和可选依赖[CP/OL].
[3]ChatGPT