spring高级篇(十)

1、内嵌tomcat

boot框架是默认内嵌tomcat的,不需要手动安装和配置外部的 Servlet 容器。

简单的介绍一下tomcat服务器的构成:

  • Catalina: Catalina 是 Tomcat 的核心组件,负责处理 HTTP 请求、响应以及管理 Servlet 生命周期。它包括一个 Web 容器和一个 Servlet 引擎,用于处理 Servlet 和 JSP 页面。
  • Connector: 连接器是 Tomcat 与外部客户端之间通信的桥梁,负责处理传入的 HTTP 请求,并将其传递给 Catalina 处理。Tomcat 提供了多种类型的连接器,包括 HTTP 连接器(用于处理 HTTP 请求)、AJP 连接器(用于与 Apache HTTP Server 连接)等。
  • Realm: Realm 是 Tomcat 的安全认证和授权机制,用于验证用户身份并控制用户对受保护资源的访问权限。Tomcat 支持多种类型的 Realm,如基于内存的 Realm、基于数据库的 Realm 等。
  • Valves: 阀门是 Tomcat 的拦截器组件,用于在请求处理过程中执行特定的操作,如访问日志记录、安全验证、压缩等。Tomcat 提供了多种类型的阀门,可以通过配置文件进行灵活配置。
  • Host: Host 是 Tomcat 的虚拟主机,用于在同一物理服务器上托管多个域名或应用程序。每个 Host 都有一个唯一的名称和基础目录,可以配置不同的域名和应用程序。
  • Engine: Engine 是 Tomcat 的引擎,用于管理多个虚拟主机(Host)。它负责调度请求到相应的虚拟主机,并协调虚拟主机之间的资源共享和管理。
  • Context: Context 是 Tomcat 的上下文容器,用于管理和配置单个 Web 应用程序的运行环境。每个 Web 应用程序都有一个对应的 Context,包括其配置信息、Servlet 映射、Session 管理等。

我们来模拟一下tomcat的执行过程:

其中第四步是将下面自定义的servlet程序放入servletContext上下文中,并且手动指定映射路径(相当于Controller层加入@RequestMapping及派生注解指定路径)

复制代码
public class A36 {
    public static void main(String[] args) throws IOException, LifecycleException {
        //1.创建tomcat对象
        Tomcat tomcat = new Tomcat();
        tomcat.setBaseDir("tomcat");

        //准备docBase,存放项目文件
        File docBase = Files.createTempDirectory("boot.").toFile();
        docBase.deleteOnExit();

        //3.创建tomcat项目 context
        Context context = tomcat.addContext("", docBase.getAbsolutePath());

     
        //4.编程添加servlet
        context.addServletContainerInitializer(new ServletContainerInitializer() {
            @Override
            public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
                servletContext.addServlet("test",new MyServlet()).addMapping("/test");
                }
            }
        }, Collections.emptySet());

        //5.启动tomcat
        tomcat.start();


        //6.设置协议,创建连接器
        Connector connector = new Connector(new Http11Nio2Protocol());
        connector.setPort(8080);
        tomcat.setConnector(connector);
    }
}

编写一个自定义的servlet程序:

复制代码
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().print("abc");
    }
}

而内嵌的tomcat如何与Spring进行整合?

其关键点在于Config配置类中的 public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) 方法。这个方法用于注册DispatcherServlet springmvc入口并创建一个 DispatcherServletRegistrationBean Bean,将传入的 DispatcherServlet 注册到 Spring 应用程序中。

Config配置类又是在refresh容器前注册的:

复制代码
 public static WebApplicationContext webApplicationContext(){
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(Config.class);
        context.refresh();
        return context;
    }

我们只需要在编程添加servlet这一步,获取容器中所有的bean,并且通过.onStartup() 方法完成注册:

复制代码
     //得到applicationContext
        WebApplicationContext applicationContext = webApplicationContext();

    //4.编程添加servlet
        context.addServletContainerInitializer(new ServletContainerInitializer() {
            @Override
            public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
                servletContext.addServlet("test",new MyServlet()).addMapping("/test");


                //得到ServletRegistrationBean中每一个注册的bean
                for (ServletRegistrationBean value : applicationContext.getBeansOfType(ServletRegistrationBean.class).values()) {
                    value.onStartup(servletContext);
                }
            }
        }, Collections.emptySet());

.onStartup() 方法底层调用的依旧是 servletContext.addServlet() 方法

boot在整合tomcat时,首先是创建了Spring容器,然后在调用onfresh()方法时会将tomcat创建出来,并且执行到添加servlet。

在finishRefresh(); 方法会启动tomcat服务器并且设置协议,创建连接器。

2、自动装配原理

现在有如下的场景:在某个包下创建了两个bean,并且将其注册到了两个配置类中:

复制代码
public class Bean1 {

}

public class Bean2 {
}

@Configuration
public class Config1 {

    @Bean
    public Bean1 bean1(){
        return new Bean1("第三方");
    }
}

@Configuration
public class Config2 {

    @Bean
    public Bean2 bean2(){
        return new Bean2();
    }
}

假设这两个bean都是与数据库连接有关的组件,具有通用性。那么难道是每次在别的地方用到的时候,都去临时注册吗?答案肯定是否定的,就和方法封装一样,我们可以在其他运用到的地方进行导入:

@Import 注解的作用就是导入其他配置类或组件类,如果在 @Import 注解中将Config1和Config2 的class 写死,这样不太好:

复制代码
@Configuration
@Import(MyInportSelector.class)
public class MyConfig {

    @Bean
    public Bean1 bean1(){
        return new Bean1("本项目");
    }
}

我们可以将Config1和Config2注册在自定义的MyInportSelector类中统一管理:

在Spring中,某个自定义ImportSelector类下需要统一装配的组件,不是写死在自定义ImportSelector类中的,而是放在 META-INF下的spring.factories中统一进行管理。

复制代码
/**
 * 读取配置文件中的引用
 */
public class MyInportSelector implements ImportSelector {

    /**
     * 在方法中会读取所有jar包 META-INF下的spring.factories 做自动装配
     * @param importingClassMetadata
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        //需要自动装配的类不是写死在代码中的,而是放在配置文件中的
        //从配置文件中读取信息 META-INF下的spring.factories
        //com.itbaima.a37.MyInportSelector=\
        //com.itbaima.a37_1.Config1,\
        //com.itbaima.a37_1.Config2
        List<String> strings = SpringFactoriesLoader.loadFactoryNames(MyInportSelector.class, null);
        return strings.toArray(new String[0]);
    }
}

完成EnableAutoConfiguration的自动装配:

复制代码
/**
 * 读取配置文件中的引用
 */
public class MyInportSelector implements ImportSelector {

    /**
     * 在方法中会读取所有jar包 META-INF下的spring.factories 做自动装配
     * @param importingClassMetadata
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        for (String name : SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, null)) {
            System.out.println(name);
        }

    }
}

那如果在本项目注册的bean和其他外部引用的bean同名的问题呢?

生效的是本项目中的。因为bean加载的时机不同,第三方的bean先加载,后加载的bean会覆盖先加载的同名的bean。然而在boot中默认是false不允许覆盖。解决方法:

自定义的InportSelector实现DeferredImportSelector接口,可以推迟第三方bean的加载。并且需要在注册第三方bean时加上 @ConditionalOnMissingBean 注解,表明当容器中没有该名称的bean时才需要加载。(因为此时本项目中的bean已先于第三方的bean加载)

复制代码
@Configuration
public class Config1 {

    @Bean
    @ConditionalOnMissingBean//当容器中缺少某个bean时才会添加
    public Bean1 bean1(){
        return new Bean1("第三方");
    }
}

3、AopAutoConfiguration

AopAutoConfiguration是用于负责配置和启用 AspectJ 面向切面编程(AOP)功能。

我们模拟一下它的自动装配:

复制代码
@Configuration
@Import(MyImportSelector.class)
public class Config {
}

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{AopAutoConfiguration.class.getName()};
    }
}

public class A38 {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        //注册各种后处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.registerBean(Config.class);
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }
}

下面这四条便是AopAutoConfiguration通过自动装配得到的BeanDefinitionName

org.springframework.boot.autoconfigure.aop.AopAutoConfigurationAspectJAutoProxyingConfigurationCglibAutoProxyConfiguration

org.springframework.aop.config.internalAutoProxyCreator

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

那么AopAutoConfiguration是如何选择装配哪些bean的呢?我们点进AopAutoConfiguration的源码看一下:

@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true) 是一个针对properties配置文件内容的判断,此处判断的含义是,如果配置文件中没有spring.aop前缀的键,或者有并且它的值为true时,会进入这个类。显然目前的条件是成立的,我们自定义的配置类中没有spring.aop前缀的键。

在静态内部类AspectJAutoProxyingConfiguration上也有一个注解:@ConditionalOnClass(Advice.class) 作用是判断是否存在一个名为Advice的类,在boot中是存在的,所以会进入AspectJAutoProxyingConfiguration类:

在JdkDynamicAutoProxyConfiguration和CglibAutoProxyConfiguration静态内部类上,分别有两个注解:

  • @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false") 判断properties配置文件中是否有spring.aop前缀的键,并且值要为false。
  • @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) 判断properties配置文件中是否有spring.aop前缀的键,并且值要为ture,或者不存在。

而两个静态内部类上标注的@EnableAspectJAutoProxy 注解,实际上也是加上了@Import

注解的自动装配配置类:

自定义的自动装配了类实现了ImportBeanDefinitionRegistrar接口,用于用编程的方式确定装配的内容:

在AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); 方法中,实际上是注册了AnnotationAwareAspectJAutoProxyCreator:

复习一下(第三篇): AnnotationAwareAspectJAutoProxyCreator是用于自动创建代理以实现切面功能的Spring后处理器,将高级的Aspect切面分解并转换成低级的Advice切面,并且根据设置去选择JDK或CGLIB代理方式(proxyTargetClass属性),在AnnotationAwareAspectJAutoProxyCreator中,有两个重要的方法:

  • protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName):用于查找符合条件的切面通知器(Advisors)。
  • protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey):内部调用 .findEligibleAdvisors得到存放符合条件切面通知器的集合,如果集合不为空,就创建代理。

根据条件,应该会自动装配CglibAutoProxyConfiguration:

可从容器中获取AnnotationAwareAspectJAutoProxyCreator,isProxyTargetClass的取值是true,代表无论是否实现了接口,走的都是CGLIB代理方式。

复制代码
AnnotationAwareAspectJAutoProxyCreator proxyCreator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
System.out.println(proxyCreator.isProxyTargetClass());

4、DataSource

在DataSourceAutoConfiguration中,主要有两部分:

  • EmbeddedDatabaseConfiguration:Spring Framework 中用于配置内嵌式数据库(Embedded Database)的自动配置类之一
  • PooledDataSourceConfiguration:Spring Framework 中用于配置连接池数据源(Pooled DataSource)的自动配置类之一。

EmbeddedDatabaseConfiguration很少用到,我们重点看PooledDataSourceConfiguration:

DataSourceConfiguration的每个静态内部类上都加入了@ConditionalOnClass 条件判断注解。

Boot中默认是有HikariDataSource源的:

所以会将HikariDataSource注册成bean并设置连接信息:

5、MybatisAutoConfiguration

在MybatisAutoConfiguration类上,有如下的注解:

  • @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) :表明必须要存在SqlSessionFactory和SqlSessionFactoryBean两个类。因为mybatis与boot整合需要数据源连接信息。
  • @ConditionalOnSingleCandidate(DataSource.class) :表明数据源必须是唯一的,不能存在多份不同的数据源
  • @EnableConfigurationProperties({MybatisProperties.class}) : 表明将来会创建一个MybatisProperties对象,用于将环境中的键值信息与对象绑定(要求配置中的键名必须前缀mybatis):
  • @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) : 表明该配置类是在DataSourceAutoConfiguration、MybatisLanguageDriverAutoConfiguration加载完成后再进行初始化。

上述任何一个条件不满足都不会进入MybatisAutoConfiguration:

public SqlSessionFactory sqlSessionFactory(DataSource dataSource) 方法用于创建SqlSessionFactory的bean,注意加上了@ConditionalOnMissingBean 代表容器中没有其他第三方的SqlSessionFactory的bean时,才会初始化MybatisAutoConfiguration中的SqlSessionFactory。

public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) 方法用于创建SqlSession模板:

SqlSessionTemplate是线程安全的,在同一个线程中,不同方法可以共用一个实例。

最后还有一个MapperScannerRegistrarNotFoundConfiguration内部类:

它只有在没有自定义的MapperFactoryBean和MapperScannerConfigurer时才会生效:

然后通过@Import(AutoConfiguredMapperScannerRegistrar.class) 注解导入了AutoConfiguredMapperScannerRegistrar,作用是扫描和引导类同包下的被@Mapper注解控制的mapper/dao接口,将这些定义为bean

6、DataSourceTransactionManagerAutoConfiguration

DataSourceTransactionManagerAutoConfiguration是用于自动配置数据源事务管理器(DataSource Transaction Manager)的类之一。

  • 在类上标注了@ConditionalOnClass({ JdbcTemplate.class, TransactionManager.class }) 注解,要求必须要有事务管理器和JDBC模板方法。
  • @EnableConfigurationProperties(DataSourceProperties.class) 会将配置文件中以spring.datasource为前缀的key绑定到DataSourceProperties对象中(设置数据库连接信息,username,password...)

并且导入了EnableConfigurationPropertiesRegistrar自动装配类。

同样JdbcTransactionManagerConfiguration静态内部类限定容器中只能有一份数据源:

  • DataSourceTransactionManager:创建处理与JDBC 数据源相关的事务的Bean。

下面介绍一些与整合Spring MVC相关的配置类:

7、TransactionAutoConfiguration

TransactionAutoConfiguration 是用于自动配置事务管理的类之一。

  • @ConditionalOnClass(PlatformTransactionManager.class) 表明容器中必须要有PlatformTransactionManager类,简单来说,它定义了事务管理器的核心功能。
  • @AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,DataSourceTransactionManagerAutoConfiguration.class, Neo4jDataAutoConfiguration.class }) 表明TransactionAutoConfiguration是在@AutoConfigureAfter 注解value中的类初始化成后再加载。
  • @EnableConfigurationProperties(TransactionProperties.class) 可以将配置文件中以spring.transaction为前缀的key绑定到TransactionProperties对象上。

并且导入了EnableConfigurationPropertiesRegistrar自动装配类

TransactionAutoConfiguration类中,提供了声明式和响应式事务的支持:

以及创建事务管理器:

还有配置声明式事务的管理:

8、ServletWebServerFactoryAutoConfiguration

是 Spring Boot 中负责配置 Servlet 容器工厂的自动配置类之一:

  • @ConditionalOnClass(ServletRequest.class):ServletRequest类存在,条件成立。(ServletRequest定义了客户端向服务器发送的 HTTP 请求的主要属性和操作。它是一个核心组件,用于在服务器端处理 HTTP 请求,HttpServletRequest就是它的子类)
  • @ConditionalOnWebApplication(type = Type.SERVLET) :应用程序是一个 Servlet Web 应用程序时,条件成立
  • @EnableConfigurationProperties(ServerProperties.class):用于启用特定类型的配置属性绑定到ServerProperties对象中。

这个类中主要注册了两个Bean:

  • ServletWebServerFactoryCustomizer:这个方法的主要作用是根据传入的参数定制 Servlet Web 服务器工厂的行为,包括配置服务器属性、注册 Web 监听器以及处理 Cookie 的 SameSite 属性。
  • TomcatServletWebServerFactoryCustomizer:这个方法的主要作用是根据传入的参数定制 Tomcat Servlet Web服务器的行为,包括配置服务器属性、Session 会话管理、安全性等方面。

9、DispatcherServletAutoConfiguration

用于配置和启用 DispatcherServlet。DispatcherServlet 是 Spring MVC 中的中央调度器,用于处理传入的 HTTP 请求,并将它们分发到相应的处理程序(Controller)进行处理:

  • @ConditionalOnWebApplication(type = Type.SERVLET) :应用程序是一个 Servlet Web 应用程序时,条件成立
  • @ConditionalOnClass(DispatcherServlet.class) :容器中必须包含DispatcherServlet类,条件成立
  • @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class :在ServletWebServerFactoryAutoConfiguration类装配完成后( Servlet 容器工厂创建完成),装配本类。

在这个类中,较为重要的是DispatcherServletConfiguration和DispatcherServletRegistrationConfiguration两个静态内部类:

  • DispatcherServletConfiguration:主要是将DispatcherServlet注册成bean,以及一个解析客户端发送的包含文件上传的 HTTP 请求,并将上传的文件转换成可操作的对象的bean
  • DispatcherServletRegistrationConfiguration:主要作用是将DispatcherServletRegistrationBean注册成bean,并且设置tomcat容器启动时即进行DispatcherServlet初始化

10、WebMvcAutoConfiguration

主要用于配置和初始化 Spring MVC 的各种组件:

RequestMappingHandlerAdapter:

RequestMappingHandlerMapping:

ExceptionHandlerExceptionResolver:

11、ErrorMvcAutoConfiguration

主要作用是在应用程序启动时,自动配置一些默认的错误处理策略和错误页面。

此前提到的BasicErrorController(处理基本的错误页面和错误信息),ErrorPageRegistrar(转发到自定义的错误页面),在本类中都有体现:

相关推荐
跟着珅聪学java1 小时前
spring boot +Elment UI 上传文件教程
java·spring boot·后端·ui·elementui·vue
我命由我123452 小时前
Spring Boot 自定义日志打印(日志级别、logback-spring.xml 文件、自定义日志打印解读)
java·开发语言·jvm·spring boot·spring·java-ee·logback
lilye662 小时前
程序化广告行业(55/89):DMP与DSP对接及数据统计原理剖析
java·服务器·前端
徐小黑ACG2 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
SKYDROID云卓小助手3 小时前
三轴云台之相机技术篇
运维·服务器·网络·数码相机·音视频
战族狼魂5 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
xyliiiiiL6 小时前
ZGC初步了解
java·jvm·算法
杉之6 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
hycccccch7 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
wirepuller_king8 小时前
创建Linux虚拟环境并远程连接,finalshell自定义壁纸
linux·运维·服务器