网站建设属什么费用,互联网门户网站建设管理 总结,域名备案要先做网站的吗,网站建设遇到哪些危险引言
对于 Java 开发人员来说#xff0c;Spring 框架几乎是必不可少的。它是一个广泛用于开发企业应用程序的开源轻量级框架。近几年#xff0c;Spring Boot 在传统 Spring 框架的基础上应运而生#xff0c;不仅提供了 Spring 的全部功能#xff0c;还使开发人员更加便捷地…引言
对于 Java 开发人员来说Spring 框架几乎是必不可少的。它是一个广泛用于开发企业应用程序的开源轻量级框架。近几年Spring Boot 在传统 Spring 框架的基础上应运而生不仅提供了 Spring 的全部功能还使开发人员更加便捷地使用。在使用 Spring Boot 时我们经常会接触到各种 Spring Boot Starter例如 spring-boot-starter-web。只需将该依赖加入项目中我们就可以开始开发应用在引入 spring-boot-starter-data-jdbc 后只需在配置文件中填写数据库连接信息即可连接数据库。此外您还可以随意切换数据源组件依赖而无需修改业务代码。Spring Boot Starter 是如何适配的呢我们能否自己实现一个 Spring Boot Starter 呢本文将剖析 Spring Boot Starter 的原理并自定义实现一个 Spring Boot Starter 组件。
一、Spring Boot Starter 是什么
Spring Boot Starter 是 Spring Boot 中比较重要的概念 是一种依赖描述符它可以帮助您简化配置。当需要构建一个 Web 应用程序时不必再遍历所有的依赖包一个一个地添加到项目的依赖管理中而是只需要一个配置spring-boot-starter-web如以下示例 从上面示例来看我们使用了相当少的代码创建了一个 REST 应用程序。Spring 官方提供了许多 Starter同时第三方也可以自定义 Starter官方为了加以区分Starter 从名称上进行了如下规范spring-boot-starter-xxx第三方提供的 starter 名称为xxx-spring-boot-starter。
二、Spring Boot Starter 剖析
前面介绍了 Starter 的概念以及如何快速创建 REST 应用程序。只需添加一个依赖和几行代码就能完成 REST 接口开发。那么在没有 Spring Boot 和 Starter 的情况下我们该如何进行开发呢Spring Boot Starter 的工作原理又是什么接下来我们将通过开发 Web 服务和 Dubbo 服务作为例子分别剖析纯 Spring 和 Spring Boot Starter。
Spring
环境依赖 JDK 1.8 Maven 3 Tomcat 8需要依靠 Web 容器服务器才能启动 spring-webmvc 4.3.30.RELEASE dubbo 2.7.23
开发流程 首先介绍一下这是一个标准的 Maven 目录结构与demo-service依赖内容 dependencies!-- SpringMVC --dependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion4.3.30.RELEASE/version/dependencydependencygroupIdjavax.servlet/groupIdartifactIdservlet-api/artifactIdversion2.5/version/dependency!-- 此处需要导入databind包即可 jackson-annotations、jackson-core都不需要显示自己的导入了--dependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.9.8/version/dependency!-- Dubbo --dependencygroupIdorg.apache.dubbo/groupIdartifactIddubbo/artifactIdversion2.7.23/version/dependencydependencygroupIdorg.apache.curator/groupIdartifactIdcurator-x-discovery/artifactIdversion5.1.0/version/dependencydependencygroupIdorg.apache.zookeeper/groupIdartifactIdzookeeper/artifactIdversion3.8.0/version/dependency!-- Demo API --dependencygroupIdcom.demo/groupIdartifactIddemo-api/artifactIdversion1.0-SNAPSHOT/version/dependency
/dependencies 由于在 Spring XML 下还需要依靠 Java Web 和 Web 容器运行还需要web/WEB-INF/web.xmlWeb 配置文件内容配置了 SpringMVC 入口 ?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0!-- Spring监听器 --listenerlistener-classorg.springframework.web.context.ContextLoaderListener/listener-class/listenercontext-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:dubbo.xml/param-value/context-paramservletservlet-namespringmvc/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:mvc.xml/param-value/init-param/servletservlet-mappingservlet-namespringmvc/servlet-nameurl-pattern//url-pattern/servlet-mapping
/web-app SpringMVC 配置文件mvc.xml与 Dubbo 配置文件dubbo.xml ?xml version1.0 encodingutf-8 ?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:mvchttp://www.springframework.org/schema/mvcxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsdcontext:component-scan base-packagecom.demo.controller/!-- 开启 MVC 注解驱动 --mvc:annotation-driven/!-- 访问静态资源 --mvc:default-servlet-handler/
/beans ?xml version1.0 encodingutf-8 ?
beans xmlnshttp://www.springframework.org/schema/beans xmlns:dubbohttp://dubbo.apache.org/schema/dubboxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd!-- Dubbo --dubbo:application namedemo-service/dubbo:registry addresszookeeper://127.0.0.1:2181/dubbo:protocol namedubbo port20880/bean iddemoServiceImpl classcom.demo.provider.DemoServiceImpl/dubbo:service interfacecom.demo.api.DemoService refdemoServiceImpl/
/beans 编写 Controller 接口与 Dubbo RPC 接口 package com.demo.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;RestController
public class HelloController {GetMapping(value /say/hello)public HelloEntity sayHello() {return new HelloEntity(Hello World);}} package com.demo.provider;import com.demo.api.DemoService;
import com.demo.dto.HelloEntity;public class DemoServiceImpl implements DemoService {Overridepublic HelloEntity sayHello() {return new HelloEntity(Hello World);}
} 以上还无法单独运行需要将以上打包成war包放入到 Tomcat 才可运行。
剖析
从上面的开发流程中我们可以看到入口都在 web.xml 中。其中有一个监听器和一个 Servlet以及初始化参数 dubbo.xml 和 mvc.xml。在 Spring Boot 出现之前Spring 通常使用 XML 配置方式描述 Bean或者在 XML 中配置注解驱动和上下文扫描方式解析 Bean。因此我们可以看出这里有两个 XML 文件。经过分析源代码我们整理出了以下 XML 标签解析到 Bean 解析的流程。如下 由 Tomcat 启动加载web.xml并通过监听器和 Servlet 让 Spring 加载 XML 并解析。 直到BeanDefinitionParserDelegate#parseCustomElement开始解析自定义标签找到mvc:xxx或dubbo:xxx标签找到了 XML 命名空间。 DefaultNamespaceHandlerResolver处理逻辑以懒加载方式加载所有 jar 中META-INF/spring.handlers路径必须得是这个并缓存到handlerMappings通过命名空间 URI 找到与之对应的处理类SpringMVC 与 Dubbo 命名空间处理类分别为MvcNamespaceHandler和DubboNamespaceHandler MvcNamespaceHandler和DubboNamespaceHandler都分别实现了NamespaceHandler#init方法内容如下 init方法将 SpringMVC 和 Dubbo 标签对应的 BeanDefinitionParser 注册到了 NamespaceHandlerSupport#parsers 中。在上一步中DefaultNamespaceHandlerResolver 根据标签获取到了该标签的 BeanDefinitionParser从而将对应的 Bean 注册到了 Spring IOC 容器中。注册逻辑不是本文的重点这里就不再赘述。至此SpringMVC 和 Dubbo 的加载流程已经完成。
从以上加载流程中我们可以看出在没有 Spring Boot 之前Spring 主要依靠 XML 配置来启动。它会加载 XML 中的自定义标签找到对应的命名空间然后扫描 classpath 下的 META-INF/spring.handlers找到命名空间处理类来解析当前标签。
Spring Boot
环境依赖 JDK 1.8 Maven 3 spring-boot 2.6.9 dubbo 2.7.23
开发流程 目录结构与 Mavendemo-spring-boot依赖内容 dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- dubbo --dependencygroupIdorg.apache.dubbo/groupIdartifactIddubbo-spring-boot-starter/artifactIdversion2.7.23/version/dependencydependencygroupIdorg.apache.curator/groupIdartifactIdcurator-x-discovery/artifactIdversion5.1.0/version/dependencydependencygroupIdorg.apache.zookeeper/groupIdartifactIdzookeeper/artifactIdversion3.8.0/version/dependencydependencygroupIdcom.demo/groupIdartifactIddemo-api/artifactIdversion1.0-SNAPSHOT/version/dependency
/dependencies 应用程序入口DemoSpringBootApplication SpringBootApplication
public class DemoSpringBootApplication {public static void main(String[] args) {SpringApplication.run(DemoSpringBootApplication.class, args);}} application.yml文件内容只有 Dubbo 的配置 dubbo:application:name: demo-providerprotocol:port: 20880name: dubboregistry:address: zookeeper://127.0.0.1:2181 编写 Controller 接口与 Dubbo RPC 接口 package com.demo.controller;import com.demo.dto.HelloEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;RestController
public class HelloController {GetMapping(value /say/hello)public HelloEntity sayHello() {return new HelloEntity(Hello World);}} package com.demo.provider;import com.demo.api.DemoService;
import com.demo.dto.HelloEntity;DubboService
public class DemoServiceImpl implements DemoService {Overridepublic HelloEntity sayHello() {return new HelloEntity(Hello World);}
} 由于spring-boot-starter-web已经内嵌 tomcat 只需要直接运行DemoSpringBootApplication#main方法即可运行应用
剖析
从开发流程上没办法第一时间找到解析入口唯一入口就是在DemoSpringBootApplication经过源代码分析得出以下流程 应用DemoSpringBootApplication类上有SpringBootApplication注解而该注解由以下三个注解组成 SpringBootConfiguration标注当前类为一个配置类与[Configuration](https://my.oschina.net/pointdance)注解功能一致 被[Configuration](https://my.oschina.net/pointdance)注解的类对应 Spring 的 XML 版的容器。 EnableAutoConfiguration开启启动自动装配的关键由AutoConfigurationPackage与Import(AutoConfigurationImportSelector.class)组成 ComponentScan按照当前类路径扫描含有Service、Controller等等注解的类等同于 Spring XML 中的context:component-scan。 Spring Boot 自动装配由EnableAutoConfiguration导入的AutoConfigurationImportSelector类会调用SpringFactoriesLoader#loadFactoryNames从 ClassPath 下扫描所有 jar 包的META-INF/spring.factories内容由于传入的EnableAutoConfiguration.class只会返回org.springframework.boot.autoconfigure.EnableAutoConfigurationkey 的值得到一个全限定类名字符串数组configurations。 configurations经过去重与声明式排除后会进行以下进行过滤自动装配 configurations getConfigurationClassFilter().filter(configurations) 分成两部分获取过滤器和执行过滤。 getConfigurationClassFilter()也是通过SpringFactoriesLoader#loadFactoryNames在META-INF/spring.factories找到 Key 为org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的值目前只有OnBeanCondition、OnClassCondition、OnClassCondition三个过滤器。 执行过滤会根据配置类上含有ConditionOnBean、ConditionalOnClass、ConditionalOnWebApplication等等条件注解来过滤掉部分配置类。比如WebMvcAutoConfiguration指定需要在ConditionOnWebApplication下才生效。 在引入各类 Configuration 的配置类后配置类结合Bean来完成 Spring Bean 解析和注入同时 Spring Boot 还提供了许多ConditionalXXX给开发者完成灵活注入。
以上就是 Spring Boot 的自动装配过程。Spring Boot 利用被 Configuration 注解的配置类来代替 Spring XML 完成 Bean 的注入。然后SpringFactoriesLoader 会最终加载 META-INF/spring.factories 中的自动配置类实现自动装配过程。依靠“约定大于配置”的思想如果开发的 Starter 想要生效就需要按照 Spring Boot 的约定。
小结
通过对比 Spring 与 Spring Boot 的开发流程我们可以发现 Spring Boot 在完成 Web 与 Dubbo 独立应用开发时使用了相对较少的代码和配置。这得益于 Spring Boot Starter 的自动装配能力它是 Spring Boot 的主要功能。通过消除定义一些属于自动配置类部分的需求自动配置可以帮助简化开发流程并加速开发速度。
SPI
我们从上面剖析发现两者都使用了一项机制去加载引入的 jar 包中的配置文件从而加载对应类那就是SPIService Provider Interface
SPI Service Provider Interface 是 Java 内置的一种服务提供发现机制提高框架的扩展性。 Java SPI
Java 内置的 SPI 通过java.util.ServiceLoader类解析 Classpath 和 jar 包的META-INF/services目录下的以接口全限定名命名的文件并加载该文件中指定的接口实现类以此完成调用。
但是 Java SPI 会有一定不足 不能做到按需加载需要遍历所有的实现并实例化然后在循环中找到所需要的实现。 多个并发多线程使用ServiceLoader类的实例不安全 加载不到实现类时抛出并不是真正原因的异常错误难定位。
Spring SPI
Spring SPI 沿用了 Java SPI 但是在实现上和 Java SPI 存在差异但是核心机制相同在不修改 Spring 源码前提下可以做到对 Spring 框架的扩展开发。 在 Spring XML 中由DefaultNamespaceHandlerResolver负责解析spring.handlers生成 namespaceUri 和 NamespaceHandler 名称的映射等有需要时在进行实例化。 在 Spring Boot 中由SpringFactoriesLoader负责解析spring.factories文件并将指定接口的所有实现类/全限定类名返回。
Spring Boot 2.7.0
在本文中 Spring Boot 自动装配使用了 SPI 来加载到EnableAutoConfiguration所指定的自动装配的类名但在 Spring Boot2.7.0之后自动装配 SPI 机制有所改动META-INF/spring.factories将废弃同时在 Spring Boot 3 以上会将相关代码移除改动如下 新的注解AutoConfiguration代替Configuration 读取自动装配的类文件位置改为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports并且实现类全限定类名按照一行一个 由org.springframework.boot.context.annotation.ImportCandidates#load负责解析META-INF/spring/%s.imports其中%s是接口名的占位符
三、Spring Boot Stater 实践
在使用spring-boot-starter-jdbc或者spring-boot-starter-jpa等数据库操作时通常会引入一个数据库数据源连接池比如HikariCP、DBCP等同时可随意切换依赖而不需要去更改任何业务代码开发人员也无需关注底层实现在此我们自定义一个 Starter 同时也实现这种兼容。因为我们以开发一个分布式锁的 Starter 并拥有多个实现Zookeeper、Redis。 在此使用 Spring Boot 2.6.9 版本。
开发
项目结构与 Maven 依赖
└── src├── main│ ├── java│ │ └── com.demo.distributed.lock│ │ ├── api│ │ │ ├── DistributedLock.java│ │ │ └── LockInfo.java│ │ ├── autoconfigure│ │ │ ├── DistributedLockAutoConfiguration.java│ │ │ └── DistributedLockProperties.java│ │ ├── redis│ │ │ └── RedisDistributedLockImpl.java│ │ └── zookeeper│ │ └── ZookeeperDistributedLockImpl.java│ └── resources│ └── META-INF│ └── spring.factories
dependencies!-- Spring Boot 自动装配注解 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-autoconfigure/artifactId/dependency!-- 生成 META-INF/spring-configuration-metadata.json --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-configuration-processor/artifactIdoptionaltrue/optional/dependency!-- Zookeeper --dependencygroupIdorg.apache.curator/groupIdartifactIdcurator-framework/artifactIdversion5.1.0/versionscopeprovided/scope/dependencydependencygroupIdorg.apache.curator/groupIdartifactIdcurator-recipes/artifactIdversion5.1.0/versionscopeprovided/scope/dependency!-- Redis --dependencygroupIdorg.redisson/groupIdartifactIdredisson/artifactIdversion3.23.1/versionscopeprovided/scope/dependency
/dependencies
在依赖里可以看到 Zookeeper 和 Redis 依赖关系被设置为provided作用为编译与测试阶段使用不会随着项目一起发布。即打包时不会带上该依赖。该设置在 Spring Boot Starter 作用较大。
分布式锁接口与实现
接口
public interface DistributedLock {/*** 加锁*/LockInfo tryLock(String key, long expire, long waitTime);/*** 释放锁*/boolean release(LockInfo lock);}
Redis 实现
public class RedisDistributedLockImpl implements DistributedLock {private final RedissonClient client;public RedisDistributedLockImpl(RedissonClient client) {this.client client;}Overridepublic LockInfo tryLock(String key, long expire, long waitTime) {//do somethingreturn null;}Overridepublic boolean release(LockInfo lock) {//do somethingreturn true;}
}
Zookeeper 实现
public class ZookeeperDistributedLockImpl implements DistributedLock {private final CuratorFramework client;public ZookeeperDistributedLockImpl(CuratorFramework client) {this.client client;}Overridepublic LockInfo tryLock(String key, long expire, long waitTime) {return null;}Overridepublic boolean release(LockInfo lock) {return false;}
}
DistributedLockAutoConfiguration 配置类
EnableConfigurationProperties(DistributedLockProperties.class)
Import({DistributedLockAutoConfiguration.Zookeeper.class, DistributedLockAutoConfiguration.Redis.class})
public class DistributedLockAutoConfiguration {ConfigurationConditionalOnClass(CuratorFramework.class)ConditionalOnMissingBean(DistributedLock.class)ConditionalOnProperty(name distributed.lock.type, havingValue zookeeper,matchIfMissing true)static class Zookeeper {BeanCuratorFramework curatorFramework(DistributedLockProperties properties) {//build CuratorFramework clientreturn null;}BeanZookeeperDistributedLockImpl zookeeperDistributedLock(CuratorFramework client) {return new ZookeeperDistributedLockImpl(client);}}ConfigurationConditionalOnClass(RedissonClient.class)ConditionalOnMissingBean(DistributedLock.class)ConditionalOnProperty(name distributed.lock.type, havingValue redis,matchIfMissing true)static class Redis {BeanRedissonClient redissonClient(DistributedLockProperties properties) {//build RedissonClient clientreturn null;}BeanRedisDistributedLockImpl redisDistributedLock(RedissonClient client) {return new RedisDistributedLockImpl(client);}}
} EnableConfigurationProperties(DistributedLockProperties.class)开启配置类 Properties 信息会将配置文件里的信息注入 Properties 类里。 Configuration配置注解 ConditionalOnClass(CuratorFramework.class)条件注解要求存在CuratorFramework类当前配置类才生效Redis 的子配置类同理。 ConditionalOnMissingBean(DistributedLock.class)条件注解Spring 不存在DistributedLockBean 当前配置类才生效Redis 的子配置类同理。 ConditionalOnProperty(name distributed.lock.type, havingValue zookeeper, matchIfMissing true)条件注解这里判断配置文件distributed.lock.type等于zookeeper才生效当如果没配置则默认当做zookeeperRedis 的子配置类同理。 Bean将方法返回的 Bean 注入到 Spring IOC 容器里方法入参中含依赖的 Bean
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration\com.demo.distributed.lock.autoconfigure.DistributedLockAutoConfiguration
我们只需要将该文件放到resource/META-INF/spring.factories下就会被 Spring Boot 加载这也是 Spring Boot 的约定大于配置的思想。
使用
Maven 依赖关系
dependenciesdependencygroupIdcom.demo/groupIdartifactIddistributed-lock-spring-boot-starter/artifactIdversion1.0.0-SNAPSHOT/version/dependency
/dependenciesprofilesprofileiddev/idactivationactiveByDefaulttrue/activeByDefault/activationdependencies!-- Redis --dependencygroupIdorg.redisson/groupIdartifactIdredisson/artifactIdversion3.23.1/version/dependency/dependencies/profileprofileidtest/iddependenciesdependencygroupIdorg.apache.curator/groupIdartifactIdcurator-framework/artifactIdversion5.1.0/version/dependencydependencygroupIdorg.apache.curator/groupIdartifactIdcurator-recipes/artifactIdversion5.1.0/version/dependency/dependencies/profile
/profiles
此处结合 Maven profile 功能按照不同环境依赖不同分布式锁底层实现同时 Spring Boot 也提供了 Spring Boot Profile 加载不同配置可以从开发、测试、生产环境使用不同底层了同时 Maven profile 可以根据-P指定加载不同的依赖进行打包解决了不同环境使用不同分布式锁实现。
代码使用
private final DistributedLock distributedLock;public DemoServiceImpl(DistributedLock distributedLock) {this.distributedLock distributedLock;
}public void test() {LockInfo lock null;try {lock distributedLock.tryLock(demo, 1000, 1000);//do something} finally {if (lock ! null) {distributedLock.release(lock);}}
}
业务代码中由于依赖的是接口结合 Spring Boot Starter 条件注解 Maven Profile 不管依赖哪个分布式锁实现都无需去修改代码。
四、总结
本文介绍了在没有 Spring Boot 和 Starter 之前开发人员在使用传统的 Spring XML 开发 Web 应用时需要引用许多依赖并且需要大量编写 XML 代码来描述 Bean 以及它们之间的依赖关系。也了解了如何利用 SPI 加载自定义标签来加载 Bean 并进行注入。而 Spring Boot Starter 则提供了一种更加现代化的配置方式它通过 SPI 机制加载自动装配的 Configuration 配置类来代替传统的 Spring XML 完成 Bean 的注入从而消除了大量的 XML 配置。最后我们通过自定义开发了一个分布式锁 Spring Boot Starter 组件利用一系列的 ConditionalXXX 注解和 Maven Profile 来完成开发。这样我们可以兼容多种分布式锁实现并且在不同环境下使用不同的分布式锁实现而无需修改业务代码。 作者京东零售 陈炎清 来源京东云开发者社区