背景:
之前一直只考虑用JavaSe进行游戏服务器开发,目前项目使用了Spring,发现还是非常好的,好处如下:
好处1:依赖注入非常方便,我们只使用Spring最基本的功能即可,这样子就算是有一些模块不使用Spring管理也是非常方便的,因为我现在已经能轻松控制住Spring容器的声明周期。
好处2: 模块之间就像搭建积木即可,又相互配合。 我想支持web也是非常轻松。
好处3: 这样子再去整合Mybatis、或者其它的一些MQ、ES之类的中间件,就太简单了。
1.net模块
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.17</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>net</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>net</name>
<packaging>pom</packaging>
<description>net</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
</dependencies>
</project>
TcpServer.java
package com.example.net;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TcpServer {
private String host;
private int port;
public TcpServer(String host, int port) {
this.host = host;
this.port = port;
}
public void start1() {
log.info("TcpServer start1 {}:{}", host, port);
}
public void shutdown1() {
log.info("TcpServer shutdown1");
}
}
GameRedisClient.java
package com.example.net;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.sync.RedisCommands;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@SuppressWarnings("all")
public class GameRedisClient {
private final RedisCommands redisCommands;
public GameRedisClient(String host, int port) {
RedisURI redisURI = RedisURI.builder()
.withHost(host)
.withPort(port)
.withTimeout(Duration.of(10, ChronoUnit.SECONDS))
.build();
RedisClient redisClient = RedisClient.create(redisURI);
RedisCommands redisCommands = redisClient.connect().sync();
this.redisCommands = redisCommands;
}
public <K, V> V get(K k) {
return (V) redisCommands.get(k);
}
public <K, V> void set(K k, V v) {
redisCommands.set(k, v);
}
}
2.应用层
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.17</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>gameserver</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gameserver</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>net</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.48</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-base:latest</builder>
</image>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
GameServer.java
package com.example;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.io.IOException;
@Slf4j
@SpringBootApplication
public class GameServer {
public static void main(String[] args) {
// 启动Spring容器
SpringApplication.run(GameServer.class, args);
// jmx阻塞关服
try {
System.in.read();
} catch (IOException e) {
log.error("exception", e);
}
}
}
ModuleConfig.java
package com.example.core.config;
import com.example.net.GameRedisClient;
import com.example.net.TcpServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 引入子模块对象
*/
@Configuration
public class ModuleConfig {
/**
* 游戏Redis
*/
@Bean
public GameRedisClient getGameRedis() {
return new GameRedisClient("localhost", 6379);
}
/**
* 长链接服务器
*/
@Bean
public TcpServer getTcpServer() {
return new TcpServer("localhost", 6080);
}
}
EModuleOrder.java
package com.example.core.enums;
/**
* 越靠上越先加载,各个模块的启动
*/
public enum EModuleOrder {
TCP_SERVER,
GAME_REDIS,
}
GameRedisModule.java
package com.example.core.module;
import com.alibaba.fastjson.JSON;
import com.example.core.enums.EModuleOrder;
import com.example.net.GameRedisClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class GameRedisModule implements ApplicationListener<ApplicationContextEvent>, Ordered {
@Autowired
GameRedisClient gameRedisClient;
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
log.info("GameRedisModule start");
// 简单类型
gameRedisClient.set("123", "abc");
String v = gameRedisClient.get("123");
log.info("{}", v);
// 复杂类型
Data data = new Data();
data.map.put("k", 6666);
gameRedisClient.set("obj", JSON.toJSONString(data));
Data obj = JSON.parseObject(gameRedisClient.get("obj"), Data.class);
log.info("{}", obj);
} else if (event instanceof ContextClosedEvent) {
log.info("GameRedisModule shutdown");
}
}
@Override
public int getOrder() {
return EModuleOrder.GAME_REDIS.ordinal();
}
private static class Data {
public int num = 1;
public Map<String, Integer> map = new HashMap<>();
@Override
public String toString() {
return "Data{" +
"num=" + num +
", map=" + map +
'}';
}
}
}
TcpServerModule.java
package com.example.core.module;
import com.example.core.enums.EModuleOrder;
import com.example.net.TcpServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class TcpServerModule implements ApplicationListener<ApplicationContextEvent>, Ordered {
@Autowired
TcpServer tcpServer;
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
tcpServer.start1();
} else if (event instanceof ContextClosedEvent) {
tcpServer.shutdown1();
}
}
@Override
public int getOrder() {
return EModuleOrder.TCP_SERVER.ordinal();
}
}
感悟:
这样子,我们使用了SpringBoot很少的功能,就去组织我们的代码,各个模块像组件一样搭建起来。
应用层则启动类非常简单,我们就可以不断提供子模块,以强大我们的应用层。
引入MQ什么的也是非常简单了。