代码拉取完成,页面将自动刷新
基于netty和spring手写一个聊天服务
####软件架构说明 服务端和客户端采用websocket长连接的方式进行服务的请求和推送。
如果采用传统的netty接收请求,根据请求的某个字段判断到底应该走哪个具体业务类,得写各种if else, 显然这么做是不优雅的,可以采用策略模式,每一种类型的消息写一个处理类,但是策略模式的缺点就是类会很多 ,那么可以做到像spring mvc那种根据url去映射走具体的controller方式吗,于是自己封装了框架,把这些 if else 从代码种解耦了出来
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Accept {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AcceptMapping {
String value() default "";
}
//基于spring框架 扫描带有自定义注解的类缓存起来
@Component
public class AcceptPostProcess implements InitializingBean, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() {
String[] beanNames = applicationContext.getBeanNamesForAnnotation(Accept.class);
try {
for (String beanName : beanNames) {
String baseUrl = "";
Object bean = applicationContext.getBean(beanName);
Class<?> clazz = bean.getClass();
if (clazz.isAnnotationPresent(AcceptMapping.class)) {
AcceptMapping acceptMapping = clazz.getAnnotation(AcceptMapping.class);
baseUrl = acceptMapping.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(AcceptMapping.class)) {
continue;
}
AcceptMapping acceptMapping = method.getAnnotation(AcceptMapping.class);
String regex = ("/" + baseUrl + acceptMapping.value()).replaceAll("/+", "/");
Pattern pattern = Pattern.compile(regex);
AcceptRespository.addBean(new AcceptBean(bean, method, pattern));
System.out.println("mapping " + regex + "," + method);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//通过反射技术,根据入参的messageUrl字段找到对应的accept并且执行方法
@Component
public class AcceptInvoke<T extends ChatMessage> {
public Response invoke(JSONObject param, ChannelHandlerContext ctx) throws Exception {
String messageType = param.getString("messageUrl");
if (StringUtils.isEmpty(messageType)) {
throw new RuntimeException("messageUrl字段不能为空");
}
AcceptBean acceptBean = getAcceptBean(messageType);
Method method = acceptBean.getMethod();
if (acceptBean == null) {
throw new RuntimeException("message路径不正确");
}
Class<?>[] paramTypes = method.getParameterTypes();
Object[] paramValues = new Object[paramTypes.length];
Map<String, Integer> paramMap = acceptBean.getParamIndexMapping();
//容器启动时已经做过判断了,这里只有两种入参 ChatMessage和 ChannelHandlerContext
if (paramMap.containsKey(ChannelHandlerContext.class.getName())) {
int index = paramMap.get(ChannelHandlerContext.class.getName());
paramValues[index] = ctx;
}
Class<T> c = getClass(acceptBean.getMethod());
if (paramMap.containsKey(c.getName())) {
int index = paramMap.get(c.getName());
paramValues[index] = JSON.toJavaObject(param, c);
}
//反射执行方法
Object result = method.invoke(acceptBean.getAccept(),paramValues);
if (result != null) {
if (!(result instanceof Response)) {
throw new RuntimeException("返回类必须为Response");
}
}
return (Response) result;
}
private AcceptBean getAcceptBean(String messageUrl) {
List<AcceptBean> acceptBeans = AcceptRespository.getAcceptBeans();
if (CollectionUtils.isEmpty(acceptBeans)) {
return null;
}
messageUrl = messageUrl.replaceAll("/+", "/");
for (AcceptBean acceptBean : acceptBeans) {
try {
Matcher matcher = acceptBean.getPattern().matcher(messageUrl);
if (!matcher.matches()) {
continue;
}
return acceptBean;
} catch (Exception e) {
throw e;
}
}
return null;
}
private Class getClass(Method method) {
Class<?>[] paramTypes = method.getParameterTypes();
for (Class<?> c : paramTypes) {
if (ChatMessage.class.isAssignableFrom(c)) {
return c;
}
}
throw new RuntimeException("accept方法参数不正确");
}
}
@AcceptMapping(value = "/chat")
@Accept
@Component
public class ChatAccept {
@Autowired
private ChatService singleChatService;
/**
* 单聊
* @param msg
*/
@AcceptMapping(value = "/single")
public void singleChat(SingleMsg msg){
singleChatService.singleChat(msg);
}
/**
* 群聊
* @param msg
*/
@AcceptMapping(value = "/group")
public void groupChat(GroupChatMsg msg){
singleChatService.groupChat(msg);
}
}
整个过程和spring mvc类似,但是为了避免长连接的资源利用,实际工作中不建议所有的通信都走socket ,还是建议走http协议
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。