概述:将前端发送的数据在后台对应数据模型并响应该以该模型为唯一参数的方法
具体阐述:在前后端通信的过程中,springmvc中使用mapping对应请求路径的方式来进行找到对应的处理过程。而这次我们通过数据模型的方式来选择对应的方法即选择对应的处理过程。
设计概述:
1.定义一个message.xml文件作为前后端通用的类模型声明(客户端发送模型以及数据给服务器,服务器发送object给客户端)。
2、定义个方法注解,将用于客户端服务器通信的方法收集起来便于查找。
3、客户端发送模型以及数据后,服务器进行数据的填充并在之前收集的容器中查找对应的方法并执行。
具体设计
一、message.xml文件设计
约束结构
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.org/message"
xmlns:tns="http://www.example.org/message"
elementFormDefault="qualified">
<element name="message">
<complexType>
<sequence>
<element name="Catalog" minOccurs="0" maxOccurs="unbounded">
<complexType>
<attribute name= "id" type="int"></attribute>
<attribute name= "clazz" type="string"></attribute>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
message.xml结构
<?xml version="1.0" encoding="UTF-8"?>
<message xmlns="http://www.example.org/message"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.example.org/message message.xsd">
<Catalog id="1" clazz="com.xxx.handle.packet.CM_First"/>
<Catalog id="2" clazz="com.xxx.handle.packet.CM_Second"/>
<Catalog id="3" clazz="com.xxx.handle.packet.CM_Third"/>
</message>
一个id对应一个数据存储类型,前端发送数据时可以发送编号。
二、具体逻辑处理过程
定义@HandleAno方法注解用于区分通信方法。
/**
* 扫描标记的方法,用传递参数对应方法,当接收到该参数时就运行
* @author jiahua
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HandledAno {
}
注解扫描
public class MethodBeanPostPocessor extends InstantiationAwareBeanPostProcessorAdapter{
@Autowired
public HandleAnnoManger manger;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Method[] methods = bean.getClass().getMethods();
for(int i=0; i < methods.length; i++) {
if(methods[i].isAnnotationPresent(HandledAno.class)) {
Type[] genericParameterTypes = methods[i].getGenericParameterTypes();
if(verifyPara(genericParameterTypes)) {
String key = genericParameterTypes[0].getTypeName();
addMapping(key,methods[i],bean);
}
}
}
return super.postProcessBeforeInitialization(bean, beanName);
}
private boolean verifyPara(Type[] genericParameterTypes) {
// 参数个数限制
if(genericParameterTypes.length != 1) {
return false;
}
//参数类型的全限定名称是否包含CM_
if(!genericParameterTypes[0].getTypeName().contains("CM_")) {
return false;
}
return true;
}
// 将通过验证的方法保存到manger的map中
private void addMapping(String key,Method method,Object bean) {
MethodDefination defiation = manger.getMappingMethodByPara(key);
if(defiation == null) {
defiation = new MethodDefination();
defiation.setObj(bean);
defiation.setMethods(new ArrayList<Method>());
manger.put(key, defiation);
}
defiation.add(method);
}
}
InstantiationAwareBeanPostProcessorAdapter接口是BeanPostProcessor实现,这是自定义bean的通用方法。还需将这个处理器导入到spring容器中
@Configuration
@Import(MethodBeanPostPocessor.class)
public class TestHandleAnoModel {
manger:作为数据的集中存储容器
@Component
public class HandleAnnoManger {
private static final Map<String,MethodDefination> handleAnnoMethodMap = new ConcurrentHashMap<String, MethodDefination>();
private static HandleAnnoManger instance;
public MethodDefination getMappingMethodByPara(String para) {
return handleAnnoMethodMap.get(para);
}
public void put(String key,MethodDefination value) {
handleAnnoMethodMap.put(key, value);
}
public static Map<String, MethodDefination> getHandleannomethodmap() {
return handleAnnoMethodMap;
}
public HandleAnnoManger() {
if(instance == null) {
synchronized(HandleAnnoManger.class) {
if( instance == null) {
instance = this;
}
}
}
}
public static HandleAnnoManger getInstance() {
return instance;
}
}
MethodDefination作为存储请求响应类对应的全部方法,并且实现MethodIvoke接口中唯一方法。将来统一调用实现类的invoke方法来进行方法的反射运行。
public interface MethodIvoke {
void invoke(Object para);
}
三、使用netty框架搭建简单的客户端和服务器完成通信
Client:
// 相关链接 https://blog.csdn.net/u013967175/article/details/78604392。
/**
* 客户端初始化的步骤
创建Bootstrap启动辅助类,通过Builder模式进行参数配置;
创建并绑定Reactor线程池EventLoopGroup;
设置并绑定服务端Channel通道类型;
绑定服务端通道数据处理器责任链Handler;
连接特定IP和端口;
* @author jiahua
*/
public class Client {
private void connect(String host,int port){
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ClientHandler());
}
});
ChannelFuture f = b.connect(host,port).sync();
f.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
Client client = new Client();
client.connect("127.0.0.1",8008);
}
}
class ClientHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//模拟前端发送json的方式来发送信息,1 对应message的id编号,后面的json字符串对应模型数据
byte[] bytes = "1^{\"name\":\"嘉华\",\"age\":\"18\"}".getBytes();
ByteBuf buf = Unpooled.buffer(bytes.length);
buf.writeBytes(bytes);
ctx.writeAndFlush(buf);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
/**
*
* TODO 此处用来处理收到的数据中含有中文的时 出现乱码的问题
* @param buf
* @return
*/
private String getMessage(ByteBuf buf) {
byte[] con = new byte[buf.readableBytes()];
buf.readBytes(con);
try {
return new String(con, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
}
Server:
// 相关链接 https://blog.csdn.net/u013967175/article/details/78604220
/**
* 服务端初始化的步骤:
创建ServerBootstrap启动辅助类,通过Builder模式进行参数配置;
创建并绑定Reactor线程池EventLoopGroup;
设置并绑定服务端Channel通道类型;
绑定服务端通道数据处理器责任链Handler;
绑定并启动监听端口;
* @author jiahua
*/
public class Server {
public void bind(int port){
EventLoopGroup bossGroup = new NioEventLoopGroup(); //处理客户端的连接请求
EventLoopGroup workerGroup = new NioEventLoopGroup();//通道IO事件
try{
ServerBootstrap b= new ServerBootstrap();
//将bossGroup传入到AbstractBootstrap中设置到group属性上,将workGroup设置到ServerBootstrap的childGroup属性上;
b.group(bossGroup,workerGroup)
//对通道类型进行设置 异步非阻塞服务端TCP通道
.channel(NioServerSocketChannel.class)
//option操作是针对parentGroup的,而childOption是针对childGroup的。
.option(ChannelOption.SO_BACKLOG,1024)
//Bootstrap.childerHandler方法接收一个 ChannelHandler, 而我们传递的是一个 派生于ChannelInitializer的匿名类,它正好也实现了 ChannelHandler接口,因此将ChannelHandler实例赋值给ServerBootstrap的childHandler属性;
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ServerHandler());
}
});
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} catch (Exception e){
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
Server server = new Server();
server.bind(8008);
}
}
/**
* ChannelInboundHandlerAdapter和SimpleChannelInboundHandler都是用来处理输入流的,
* SimpleChannelInboundHandler继承自ChannelInboundHandlerAdapter,
* 只是对传入的数据已经进行了强制类型转换,在输入流处理上两者都能实现;
* @author jiahua
*
*/
class ServerHandler extends ChannelInboundHandlerAdapter{
//message.xml 解析成map
private static final Map<String,String> map = new HashMap<String, String>();
static {
domParse();
}
//服务器接收到客户端发来的消息是会触发这个方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String command = new String(bytes,"UTF-8");
dealReflac(command);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
private void dealReflac(String command) {
int index = command.indexOf("^");
//前端发来数据对应后台数据存储模型的编号
String id = command.substring(0, index);
//具体的数据
String paraJson = command.substring(index+1);
//根据id编号找到对应数据存储模型
String clazz = map.get(id);
//根据类的全限定名称 找出被HandleAno标记并以该类为唯一参数的方法。
MethodDefination mappingMethod = HandleAnnoManger.getInstance().getMappingMethodByPara(clazz);
//将前端传送来的json字符数据 转化成对应的数据存储类型,并以此作为方法反射执行的参数
Object obj= dealObject(clazz,paraJson);
if(obj == null) {
return ;
}
mappingMethod.invoke(obj);
}
//参数字符串转化
private Object dealObject(String clazz,String paraJson) {
Object obj = null;
try {
//根据类的全限定名称获取java运行类
obj = Class.forName(clazz).newInstance();
JSONObject jsonObject=JSONObject.fromObject(paraJson);
//将数据存入模型形成反射时需要用的object参数
obj = JSONObject.toBean(jsonObject,obj.getClass());
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
//解析message.xml文件
private static void domParse() {
SAXReader reader = new SAXReader();
try {
Document document = reader.read(new File("src/main/resource/message.xml"));
Element bookStore = document.getRootElement();
Iterator<?> it = bookStore.elementIterator();
while (it.hasNext()) {
Element Catalog = (Element) it.next();
List<Attribute> catalogAttrs = Catalog.attributes();
if(catalogAttrs.size() <= 1) {
return;
}
map.put(catalogAttrs.get(0).getValue(), catalogAttrs.get(1).getValue());
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
}