一、pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- pom模型版本 -->
<modelVersion>4.0.0</modelVersion>
<!-- 项目信息 -->
<groupId>demo</groupId><!-- 项目唯一标识 -->
<artifactId>springboot</artifactId><!-- 项目名 -->
<version>0.0.1-SNAPSHOT</version><!-- 版本 -->
<packaging>jar</packaging><!-- 打包方式 (pom,war,jar) -->
<name>springboot</name><!-- 项目的名称, Maven 产生的文档用 -->
<description>Demo project for Spring Boot</description><!-- 项目的描述, Maven 产生的文档用 -->
<!-- 父级项目 -->
<parent>
<artifactId>spring-boot-starter-parent</artifactId> <!-- 被继承的父项目的构件标识符 -->
<groupId>org.springframework.boot</groupId> <!-- 被继承的父项目的全球唯一标识符 -->
<version>1.5.7.RELEASE</version> <!-- 被继承的父项目的版本 -->
<!-- 父项目的pom.xml文件的相对路径。相对路径允许你选择一个不同的路径。默认值是../pom.xml。
Maven首先在构建当前项目的地方寻找父项目的pom,其次在文件系统的这个位置(relativePath位置),
然后在本地仓库,最后在远程仓库寻找父项目的pom。 -->
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 模块(有时称作子项目) 被构建成项目的一部分。列出的每个模块元素是指向该模块的目录的相对路径 -->
<modules>
<!-- 子项目相对路径 -->
<module></module>
</modules>
<!-- 属性设置 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!-- 编译字符编码为utf-8 -->
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><!-- 输出字符编码为UTF-8 -->
<java.version>1.8</java.version><!-- jdK版本 -->
</properties>
<!-- 依赖关系 -->
<dependencies>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mysql(数据库) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<!-- 编译 -->
<build>
<!-- 插件 -->
<plugins>
<!-- maven插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
二、Spring的流程
1、Spring的启动
2、Spring中bean的生命周期
(1).Spring对bean进行实例化;
(2).Spring将值和bean的引用注入到bean对应的属性中;
(3).bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
(4).bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
(5).bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
(6).bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()方法;
(7).bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,
(8).bean使用initmethod声明了初始化方法,该方法也会被调用;
(9).bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
(10).bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
(11).bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。
三、内置容器
SpringBoot提供了四种Web容器,分别为Tomcat,Jetty,Undertow,Netty。
1、Tomcat(默认)
Tomcat在8.0之前默认采⽤的I/O⽅式为BIO,之后改为NIO,适合处理少数非常繁忙的链接。
1.Tomcat组成、架构
(1)Tomcat中只有一个Server,一个Server可以有多个Service,一个Service可以有多个 Connector(链接) 和一个 Container(容器);
(2)Server 掌管着整个Tomcat的生死大权;
(4)Service 是对外提供服务的;
(5)Connector 用于接受请求并将请求封装成Request和Response来具体处理;
(6)Container 用于封装和管理Servlet,以及具体处理request请求;
2.Tomcat性能调优
namePrefix: 线程前缀
maxThreads: 最大线程数,默认设置 200,一般建议在 500 ~ 1000,根据硬件设施和业务来判断
minSpareThreads: 核心线程数,默认设置 25
prestartminSpareThreads: 在 Tomcat 初始化的时候就初始化核心线程
maxQueueSize: 最大的等待队列数,超过则拒绝请求 ,默认 Integer.MAX_VALUE
maxIdleTime: 线程空闲时间,超过该时间,线程会被销毁,单位毫秒。
3.Tomcat热加载实
调用 Context 容器的 reload 方法,先stop Context容器,再start Context容器。具体的实现:
1)停止和销毁 Context 容器及其所有子容器,子容器其实就是 Wrapper,也就是说 Wrapper 里面 Servlet 实例也被销毁了。
2)停止和销毁 Context 容器关联的 Listener 和 Filter。
3)停止和销毁 Context 下的 Pipeline 和各种 Valve。
4)停止和销毁 Context 的类加载器,以及类加载器加载的类文件资源。
5)启动 Context 容器,在这个过程中会重新创建前面四步被销毁的资源。
Context 容器对应一个类加载器,类加载器在销毁的过程中会把它加载的所有类也全部销毁。
Context 容器在启动过程中,会创建一个新的类加载器来加载新的类文件。
4.Tomcat热部署
热部署跟热加载的本质区别是,热部署会重新部署 Web 应用,原来的 Context 对象会整个被销毁掉,因此这个 Context 所关联的一切资源都会被销毁,包括 Session。
Host 容器并没有在 backgroundProcess 方法中实现周期性检测的任务,而是通过监听器 HostConfig 来实现的(HostConfig#lifecycleEvent)
HostConfig 会检查 webapps 目录下的所有 Web 应用:如果原来 Web 应用目录被删掉了,就把相应 Context 容器整个销毁掉。是否有新的 Web 应用目录放进来了,或者有新的 WAR 包放进来了,就部署相应的 Web 应用。
因此 HostConfig 做的事情都是比较“宏观”的,它不会去检查具体类文件或者资源文件是否有变化,而是检查 Web 应用目录级别的变化。
2、Jetty
开源的webserver/servlet容器,是基于 NIO模型。
通过Handler实现扩展简单。Jetty和Tomcat性能方面差异不大,Jetty可以同时处理大量连接而且可以长时间保持连接,适合于web聊天应用等。
1.替换默认的Tomcat容器
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
<exclusions>
<!-- 去除Tomcat容器 -->
<exclusion>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-tomcat</artifactid>
</exclusion>
</exclusions>
</dependency>
<!-- 增加Jetty容器 -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-jetty</artifactid>
</dependency>
</dependencies>
2.重要参数
是否打开Jetty日志(默认关闭):server.jetty.accesslog.enabled
访问日志所在目录: server.jetty.accesslog.dir
最大线程数: server.jetty.threads.max
最小线程数: server.jetty.threads.min
最大队列容量: server.jetty.threads.max-queue-capacity
线程最大空闲时间: server.jetty.threads.idle-timeout
3、Undertow
轻量级:Undertow 是非常小的,只有不到1MB。在内嵌模式下,运行时只占heap空间的4MB左右。
支持 Servlet 3.1
Web Socket:支持 Web Socket (包括JSR-356)
长连接:默认情况下,Undertow 通过添加keep-alive 的response header来支持长连接。它通过重用连接信息(connection details)来改善长连接的性能。
1.替换默认的Tomcat容器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 去除 Tomcat 容器 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加 Undertow 容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
2.重要参数
# Undertow 日志存放目录
server.undertow.accesslog.dir=
# 是否启动日志
server.undertow.accesslog.enabled=false
# 日志格式
server.undertow.accesslog.pattern=common
# 日志文件名前缀
server.undertow.accesslog.prefix=access_log
# 日志文件名后缀
server.undertow.accesslog.suffix=log
# HTTP POST请求最大的大小
server.undertow.max-http-post-size=0
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
# 不要设置过大,如果过大,启动项目会报错:打开文件数过多
server.undertow.io-threads=12
# 阻塞任务线程池, 当执行类似servlet请求阻塞IO操作, undertow会从这个线程池中取得线程
# 它的值设置取决于系统线程执行任务的阻塞系数,默认值是IO线程数*8
server.undertow.worker-threads=20
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可
server.undertow.buffer-size=1024
# 每个区分配的buffer数量 , 所以pool的大小是buffer-size * buffers-per-region
server.undertow.buffers-per-region=1024
# 是否分配的直接内存
server.undertow.direct-buffers=true
4、Netty
1、SpringBoot整合Netty(服务端)
1.配置
<!-- netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
# netty 配置
netty:
# boss线程数量
boss: 4
# worker线程数量
worker: 2
# 连接超时时间
timeout: 6000
# 服务器主端口
port: 17000
# 服务器备用端口
portSalve: 18026
# 服务器地址
host: 127.0.0.1
2.编写netty处理器
/**
* Socket拦截器,用于处理客户端的行为
**/
@Slf4j
public class SocketHandler extends ChannelInboundHandlerAdapter {
public static final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* 读取到客户端发来的消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 由于我们配置的是 字节数组 编解码器,所以这里取到的用户发来的数据是 byte数组
byte[] data = (byte[]) msg;
log.info("收到消息: " + new String(data));
// 给其他人转发消息
for (Channel client : clients) {
if (!client.equals(ctx.channel())) {
client.writeAndFlush(data);
}
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
log.info("新的客户端链接:" + ctx.channel().id().asShortText());
clients.add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
clients.remove(ctx.channel());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.channel().close();
clients.remove(ctx.channel());
}
}
3.编写netty初始化器
/**
* Socket 初始化器,每一个Channel进来都会调用这里的 InitChannel 方法
**/
@Component
public class SocketInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 添加对byte数组的编解码,netty提供了很多编解码器,你们可以根据需要选择
pipeline.addLast(new ByteArrayDecoder());
pipeline.addLast(new ByteArrayEncoder());
// 添加上自己的处理器
pipeline.addLast(new SocketHandler());
}
}
4.编写netty服务
@Slf4j
@Component
public class SocketServer {
@Resource
private SocketInitializer socketInitializer;
@Getter
private ServerBootstrap serverBootstrap;
/**
* netty服务监听端口
*/
@Value("${netty.port:17000}")
private int port;
/**
* 主线程组数量
*/
@Value("${netty.boss:4}")
private int bossThread;
/**
* 启动netty服务器
*/
public void start() {
this.init();
this.serverBootstrap.bind(this.port);
log.info("Netty started on port: {} (TCP) with boss thread {}", this.port, this.bossThread);
}
/**
* 初始化netty配置
*/
private void init() {
// 创建两个线程组,bossGroup为接收请求的线程组,一般1-2个就行
NioEventLoopGroup bossGroup = new NioEventLoopGroup(this.bossThread);
// 实际工作的线程组
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
this.serverBootstrap = new ServerBootstrap();
this.serverBootstrap.group(bossGroup, workerGroup) // 两个线程组加入进来
.channel(NioServerSocketChannel.class) // 配置为nio类型
.childHandler(this.socketInitializer); // 加入自己的初始化器
}
}
5.启动netty
/**
* 监听Spring容器启动完成,完成后启动Netty服务器
**/
@Component
public class NettyStartListener implements ApplicationRunner {
@Resource
private SocketServer socketServer;
@Override
public void run(ApplicationArguments args) throws Exception {
this.socketServer.start();
}
}
2、Netty客户端
客户端用NIO来编写,在实际工作中客户端可能是 WebSocket、Socket,以 Socket 为例。
1.编写客户端线程
public class ClientThread implements Runnable{
private final Selector selector;
public ClientThread(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
try {
for (; ; ) {
int channels = selector.select();
if (channels == 0) {
continue;
}
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeySet.iterator();
while (keyIterator.hasNext()) {
SelectionKey selectionKey = keyIterator.next();
// 移除集合当前得selectionKey,避免重复处理
keyIterator.remove();
if (selectionKey.isReadable()) {
this.handleRead(selector, selectionKey);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 处理可读状态
private void handleRead(Selector selector, SelectionKey selectionKey) throws IOException {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
StringBuilder message = new StringBuilder();
if (channel.read(byteBuffer) > 0) {
byteBuffer.flip();
message.append(StandardCharsets.UTF_8.decode(byteBuffer));
}
// 再次注册到选择器上,继续监听可读状态
channel.register(selector, SelectionKey.OP_READ);
System.out.println(message);
}
}
2.客户端逻辑
public class ChatClient {
public void start(String name) throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8088));
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
// 监听服务端发来得消息
new Thread(new ClientThread(selector)).start();
// 监听用户输入
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String message = scanner.nextLine();
if (StringUtils.hasText(message)) {
socketChannel.write(StandardCharsets.UTF_8.encode(name + ": " + message));
}
}
}
}
3.客户端
public class Client1 {
public static void main(String[] args) throws IOException {
new ChatClient().start("李四");
}
}
public class Client2 {
public static void main(String[] args) throws IOException {
new ChatClient().start("张三");
}
}
三、重要组件
Spring Core:Spring的核心组件,提供IOC、AOP等基础功能,是Spring全家桶的基础。
Spring Boot:一个基于Spring Framework的快速开发框架,可以快速创建独立的、生产级别的Spring应用程序。
Spring Cloud:一个用于构建分布式应用程序的框架,提供了诸如服务发现、配置管理、负载均衡等功能。
Spring Data:用于简化数据访问层开发的框架,提供了一系列数据访问模板和持久化技术的集成。
Spring Security:一个用于处理应用程序安全的框架,提供了认证、授权、安全防护等功能。
Spring Integration:Spring Integration是一个用于构建企业级集成解决方案的框架,支持将不同的应用程序和服务集成到一起。它提供了许多组件和模式,如消息通道、消息端点、消息路由器、过滤器等。
Spring Batch:Spring Batch是一个用于处理大量数据和批处理作业的框架。它提供了各种工具和组件,如任务启动器、作业仓库、作业执行器、步骤处理器、读写器等。
Spring Web Services:Spring Web Services是一个用于构建基于SOAP协议的Web服务的框架。它提供了各种组件和工具,如消息处理器、绑定器、端点等,使得构建Web服务更加容易。