文章目录
1、创建工程
创建一个工程,包含springboot和user两个module
- springboot模块,表示springboot框架的源码实现
- user包,表示用户业务系统,用来写业务代码来测试我们所模拟出来的SpringBoot
1.1、pom依赖
SpringBoot是基于的Spring,所以我们要依赖Spring,然后我希望我们模拟出来的SpringBoot也支持Spring MVC的那一套功能,所以也要依赖Spring MVC,包括Tomcat等,所以在SpringBoot模块中要添加以下依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.60</version>
</dependency>
</dependencies>
在User模块只添加我们创建的SpringBoot的依赖:
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>springboot</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
定义相关Controller和Service
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("test")
public String test(){
return userService.test();
}
}
@Service
public class UserService {
public String test() {
return "tacy";
}
}
1.2、核心注解和核心类
在真正使用SpringBoot时,核心会用到SpringBoot一个类和注解:
- @SpringBootApplication,这个注解是加在应用启动类上的,也就是main方法所在的类
- SpringApplication,这个类中有个run()方法,用来启动SpringBoot应用的
现在也来模拟实现他们。
@TacySpringBootApplication注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface TacySpringBootApplication {
}
用来实现启动逻辑的TacySpringApplication类:
public class TacySpringApplication {
public static void run(Class clazz) {
}
}
**在MyApplication中来使用: **
@TacySpringBootApplication
public class UserApplication {
public static void main(String[] args) {
TacySpringApplication.run(UserApplication.class);
}
}
目前只是壳子出来了,还不能跑起来
1.3、run方法
目标: 希望run方法一旦执行完,就能在浏览器中访问到UserController,那势必在run方法中要启动Tomcat,通过Tomcat就能接收到请求了。
在SpringMVC中有一个Servlet非常核心,那就是DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器,因为DispatcherServlet接收到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法。
所以,在run方法中,我们要实现的逻辑如下:
- 创建一个Spring容器
- 创建Tomcat对象
- 生成DispatcherServlet对象,并且和前面创建出来的Spring容器进行绑定
- 将DispatcherServlet添加到Tomcat中
- 启动Tomcat
1.4、创建Spring容器
public class TacySpringApplication {
public static void run(Class clazz) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(clazz);
applicationContext.refresh();
}
}
- 创建的是一个AnnotationConfigWebApplicationContext容器,
- 把run方法传入进来的class作为容器的配置类,比如在UserApplication的run方法中,我们就是把UserApplication.class传入到了run方法中,最终UserApplication就是所创建出来的Spring容器的配置类
- 并且由于UserApplication类上有@TacySpringBootApplication注解, @TacySpringBootApplication注解的定义上又存在@ComponentScan注解,所以AnnotationConfigWebApplicationContext容器在执行refresh时,就会解析UserApplication这个配置类,从而发现定义了@ComponentScan注解,也就知道了要进行扫描,只不过扫描路径为空,而AnnotationConfigWebApplicationContext容器会处理这种情况,如果扫描路径会空,则会将UserApplication所在的包路径做为扫描路径,从而就会扫描到UserService和UserController。
- Spring容器创建完之后,容器内部就拥有了UserService和UserController这两个Bean。
1.5、启动Tomcat
使用内嵌的tomcat, Embed-Tomcat
public static void startTomcat(WebApplicationContext applicationContext){
Tomcat tomcat = new Tomcat();
Server server = tomcat.getServer();
Service service = server.findService("Tomcat");
Connector connector = new Connector();
connector.setPort(8081);
Engine engine = new StandardEngine();
engine.setDefaultHost("localhost");
Host host = new StandardHost();
host.setName("localhost");
String contextPath = "";
Context context = new StandardContext();
context.setPath(contextPath);
context.addLifecycleListener(new Tomcat.FixContextListener());
host.addChild(context);
engine.addChild(host);
service.setContainer(engine);
service.addConnector(connector);
tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));
context.addServletMappingDecoded("/*", "dispatcher");
try {
tomcat.start();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
- 配置了Tomcat绑定的端口为8081,
- 向当前Tomcat中添加了DispatcherServlet,并设置了一个Mapping关系,最后启动
- 在构造DispatcherServlet对象时,传入了一个ApplicationContext对象,也就是一个Spring容器,前文说的,DispatcherServlet对象和一个Spring容器进行绑定。
- 在run方法中,调用startTomcat:
public static void run(Class clazz) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(clazz);
applicationContext.refresh();
startTomcat(applicationContext);
}
现在运行Application,就能正常的启动项目,并能接收请求
在浏览器上访问:http://localhost:8081/test
1.6、实现Tomcat和Jetty的切换
上面已经实现了一个简单的SpringBoot,接下来继续扩充,实现以下需求:
- 如果项目中Tomcat的依赖,就启动Tomcat
- 如果项目中Jetty的依赖,就启动Jetty
- 如果两者都没有就报错
- 如果两者都有也报错
需求目标: 希望SpringBoot自动帮我实现,对于程序员用户而言,只要在Pom文件中添加相关依赖就可以了,想用Tomcat就加Tomcat依赖,想用Jetty就加Jetty依赖
定义一个接口,WebServer, 在这个接口中定义一个start方法
public interface WebServer {
public void start();
}
再针对Tomcat和Jetty提供2个实现类
public class TomcatWebServer implements WebServer{
@Override
public void start() {
System.out.println("启动Tomcat");
}
}
public class TomcatWebServer implements WebServer{
@Override
public void start() {
System.out.println("启动Jetty");
}
}
在TacySpringApplication中的run方法中,去获取对应的WebServer,然后启动对应的webServer,代码为:
public static void run(Class clazz){
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(clazz);
applicationContext.refresh();
WebServer webServer = getWebServer(applicationContext);
webServer.start();
}
public static WebServer getWebServer(ApplicationContext applicationContext){
return null;
}
1.7、模拟实现条件注解
实现一个条件注解@TacyConditionalOnClass
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(TacyOnClassCondition.class)
public @interface TacyConditionalOnClass {
String value() default "";
}
真正实现条件逻辑的是@Conditional(TacyOnClassCondition.class)中的TacyOnClassCondition
public class TacyOnClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(TacyConditionalOnClass.class.getName());
String className = (String) annotationAttributes.get("value");
try {
context.getClassLoader().loadClass(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
- 拿到@TacyConditionalOnClass中的value属性
- 用类加载器进行加载
- 加载到了所指定的类,就符合条件