背景:
之前一直只考虑用JavaSe进行游戏服务器开发,目前项目使用了Spring,发现还是非常好的,好处如下:
好处1:依赖注入非常方便,我们只使用Spring最基本的功能即可,这样子就算是有一些模块不使用Spring管理也是非常方便的,因为我现在已经能轻松控制住Spring容器的声明周期。
好处2: 模块之间就像搭建积木即可,又相互配合。 我想支持web也是非常轻松。
好处3: 这样子再去整合Mybatis、或者其它的一些MQ、ES之类的中间件,就太简单了。
1.net模块
pom.xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.7.17
com.example
net
0.0.1-SNAPSHOT
net
pom
net
UTF-8
11
11
11
org.springframework.boot
spring-boot-starter
org.projectlombok
lombok
true
org.projectlombok
lombok
true
io.lettuce
lettuce-core
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 V get(K k) {
return (V) redisCommands.get(k);
}
public void set(K k, V v) {
redisCommands.set(k, v);
}
}
2.应用层
pom.xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.7.17
com.example
gameserver
0.0.1-SNAPSHOT
gameserver
Demo project for Spring Boot
UTF-8
11
11
11
org.springframework.boot
spring-boot-starter
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.example
net
0.0.1-SNAPSHOT
compile
com.alibaba
fastjson
1.2.48
compile
org.springframework.boot
spring-boot-maven-plugin
paketobuildpacks/builder-jammy-base:latest
org.projectlombok
lombok
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, 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 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, 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什么的也是非常简单了。