ACTIVE_MQ_MVC
最近闲的没事,看了一系列的设计模式,以及spring的部分源码,看完后收获很多,然后又恰好接到一个消息队列采集日志的需求,于是就开始了一条不归路…
(由于项目是基于activemq,并且改造后的代码很像 spring mvc 所以我取名叫ACTIVE_MQ_MVC)
场景重现:
/**
* MessageListener回调函数.
*/
@Override
public void onMessage(Message message) {
try {
TextMessage mapMessage = (TextMessage) message;
JSONObject json = JSONObject.parseObject(mapMessage.getText());
// 消息类型
Integer type = json.getInteger("type");
// 商城来访用户
if (Constant.WXDO_LOGIN.equals(type)) {
addUserVisitRecord(json);
}
} catch (JMSException ex) {
LOG.error("记录商城消息时发生异常.", ex.getMessage(), ex);
}catch (Exception ex){
LOG.error("invokeError....", ex.getMessage(), ex);
}
}
说出来你可能不信,这段代码将运行于互联网电商项目…并且访问人数不低,如果是一年之前我肯定觉得没什么,但是现在深受上家公司组长的感染,开始有了一些代码洁癖(主要是因为闲的蛋疼,外加最近看了设计模式)…所以我决定改造下…
当时的想法是打算设计一个接口,然后具体业务类实现,通过工厂类找到具体具体实现,动态的完成调用,
逻辑简化图如下
起初,我是准备使用简单的工厂模式 + 策略者模式实现这个功能,并且已经实现了这块的代码,我在网上找了很多资料,并且参考了部分案例,由于时间久远,只找到了其中的一章博客地址,这里表示对没有发出地址的博主表示歉意,使用spring原生注解实现工厂 + 策略模式
当前计划完成度如下
工厂模式最初我想的是依赖spring注入一个Map
对应的代码很简单,也很麻烦,具体的实现逻辑在 使用spring原生注解实现工厂 + 策略模式
其中的核心代码如下
// 关键功能 Spring 会自动将 IShopLogStrategy 接口的类注入到这个Map中,key 为 bean的类名首字母小写
@Autowired
private Map<String, IShopLogStrategy> strategyMap;
其实到现在,基本的模型已经出来了,此处最大的问题就是需要本地配置一个映射Map,这就显的非常麻烦,于是我参考了spring mvc的@controller注解,只要在类上加上注解,就可以实现注解配置…
因为之前的代码逻辑很简单,所以以讲诉为主,后面会逐渐以代码为主
/**
* @author xyang
* @version 1.0
* @date 2020/5/27 15:58
* @desc 别名注解,用于ShopLogStrategy实现类配置ID
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface LogStrategyTask {
int value() default -1;
}
/**
* @author xyang
* @version 1.0
* @date 2020/5/27 14:53
* @desc 任务工厂,容器管理,别名转换,单例
*/
@Component
public class ShopLogStrategyHolder {
/**
* 执行器容器
*/
@Autowired
private List<ShopLogStrategy> shopStrategys;
public ShopLogStrategy getShopStrategy(int requireId){
// 找出id对应的日志采集器
for (ShopLogStrategy shopStrategy : shopStrategys) {
Class<? extends ShopLogStrategy> clazz = shopStrategy.getClass();
if (!clazz.isAnnotationPresent(LogStrategyTask.class)){
throw new RuntimeException("impl no find @LogStrategyTask,task require @LogStrategyTask,is must,任务类必须使用@LogStrategyTask...");
}
//id 一致就返回
LogStrategyTask alias = (LogStrategyTask)clazz.getAnnotation(LogStrategyTask.class);
int value = alias.value();
if (value == requireId){
return shopStrategy;
}
}
//没有找到对应的采集器,可能是task任务类上没有注解,或者填写错误
throw new RuntimeException("impl no find @LogStrategyTask,没有找到对应的采集器,可能Const,task不一致");
}
}
写到这,其实已经差不多可以使用了,但是缺点很大.因为每新增一个功能就要新增一个类,然后去实现接口写方法,想象一下,半年以后的是个什么情况,简直难以维护,不可描述.
为了解决上述情况,我思考了很久(越思考头发越多的那种)…
于是乎,我想到了抽象类做中间方法当跳板,于是很神奇的代码就出来了,
按上面的玩法,需要根据注解定位到对应的方法…
/**
* @author xyang
* @version 1.0
* @date 2020/5/29 16:39
* @desc TODO
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodMapping {
int value() default -1;
}
工厂类的代码如下
package com.hunt.shop.factory;
import com.hunt.shop.common.LogStrategyTask;
import com.hunt.shop.common.MethodMapping;
import com.hunt.shop.common.ShopLogUtils;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;
/**
* @author xyang
* @version 1.0
* @date 2020/5/27 14:53
* @desc 任务工厂,容器管理,别名转换,单例
*/
@Component
public class ShopLogStrategyHolder {
/**
* 执行器容器
*/
@Autowired
private List<ShopLogStrategy> shopStrategys;
public ShopLogStrategy getShopStrategy(int requireId){
// 找出id对应的日志采集器
for (ShopLogStrategy shopStrategy : shopStrategys) {
Class<? extends ShopLogStrategy> clazz = shopStrategy.getClass();
if (!clazz.isAnnotationPresent(LogStrategyTask.class)){
throw new RuntimeException("impl no find @LogStrategyTask,task require @LogStrategyTask,is must,任务类必须使用@LogStrategyTask...");
}
//id 一致就返回
LogStrategyTask alias = (LogStrategyTask)clazz.getAnnotation(LogStrategyTask.class);
//int value = alias.value();
//int value = 1;
Method[] methods = shopStrategy.getClass().getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MethodMapping.class)){
MethodMapping annotation = method.getAnnotation(MethodMapping.class);
if (annotation.value() == requireId){
// return handle
return shopStrategy;
}
}
}
}
//没有找到对应的采集器,可能是task任务类上没有注解,或者填写错误
throw new RuntimeException("impl no find @LogStrategyTask,没有找到对应的采集器,可能Const,task不一致");
}
抽象类的重写方法如下
import com.alibaba.fastjson.JSONObject;
import com.hunt.common.util.VerifyUtil;
import com.hunt.shop.common.MethodMapping;
import com.hunt.shop.factory.ShopLogStrategy;
import com.hunt.shop.record.service.business.impl.IRecordBusinessServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author xyang
* @version 1.0
* @date 2020/5/28 16:33
* @desc TODO
*/
public abstract class AbstractLogStrategyTask implements ShopLogStrategy {
private static final Logger LOG = LoggerFactory.getLogger(AbstractLogStrategyTask.class);
///**
// * @Description: 采集日志具体实现
// * @Author: xyang
// * @Date: 2020/5/28
// */
//@Override
//public void doLog(JSONObject json,int id) {
// // 定义参数容器
// Object[] userParams = null;
//
// if (parameterNames != null){
// //sealedParam
// userParams = sealedParam(parameterNames,opContent,returnData,json,method);
// }
//
// try {
// success = (boolean)method.invoke(this, userParams);
//
// } catch (Exception e) {
// //log(json);
// e.printStackTrace();
// }
//}
@Override
public void doLog(JSONObject json,int id) {
// 消息入参
String opContent = json.getString("opContent");
// 消息出参
String returnData = json.getString("returnData");
// result
boolean success = false;
// 找到对应的方法
Method[] methods = this.getClass().getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MethodMapping.class)){
MethodMapping annotation = method.getAnnotation(MethodMapping.class);
// 调用方法
if (annotation.value() == id){
// 获取所有参数
ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
// 定义参数容器
Object[] userParams = null;
if (parameterNames != null){
//sealedParam
userParams = sealedParam(parameterNames,opContent,returnData,json,method);
}
try {
success = (boolean)method.invoke(this, userParams);
} catch (Exception e) {
//log(json,opContent,returnData, id);
e.printStackTrace();
}
break;
}
} else {
// 不是映射方法
continue;
}
}
// do task
//boolean success = task(json,opContent,returnData);
if (!success){
// 记录日志
//do orther
//log(json,opContent,returnData,id);
}
}
/**
* 封装参数
* @param userParam param
* @param opContent 1
* @param returnData 2
* @param json all
* @param method clazz.getMethod
* @return
*/
public Object[] sealedParam(String[] userParam,String opContent,String returnData,JSONObject json,Method method){
//invoke data
Object[] args = new Object[userParam.length];
// all data
Map<String, Object> allData = putAll(opContent, returnData, json);
// compare data
int i = 0;
for (String param : userParam) {
Class<?> pClazz = method.getParameterTypes()[i];
// 包含key
if (allData.containsKey(param)){
// Integer 转换
if ("java.lang.Integer".equals(pClazz.getName())){
args[i] = Integer.parseInt((String) allData.get(param));
i++;
continue;
}
// Integer 转换
if ("java.lang.String".equals(pClazz.getName())){
args[i] = (String) allData.get(param);
i++;
continue;
}
// Integer 转换
if ("java.lang.Long".equals(pClazz.getName())){
Integer value = (Integer) allData.get(param);
args[i] = value.longValue();
i++;
continue;
}
}
if ("com.alibaba.fastjson.JSONObject".equals(pClazz.getName())){
args[i] = json;
i++;
continue;
}
// default null
args[i] = null;
i++;
}
return args;
}
/**
* @Description: 封装全部参数进map中
* @Author: xyang
* @Date: 2020/5/29
*/
public Map<String,Object> putAll(String opContent,String returnData,JSONObject json){
Map<String,Object> all = new HashMap<>();
if(!VerifyUtil.isEmpty(opContent)){
try {
// 数据存放到数据容器中
JSONObject jsonObject = JSONObject.parseObject(opContent);
jsonObject.keySet().forEach(t1 -> all.put(t1,jsonObject.get(t1)));
}catch (Exception e){
LOG.info("opContent is not good format");
}
}
if(!VerifyUtil.isEmpty(returnData)){
// 数据存放到数据容器中
try {
JSONObject jsonObject = JSONObject.parseObject(returnData);
jsonObject.keySet().forEach(t1 -> all.put(t1,jsonObject.get(t1)));
}catch (Exception e){
LOG.info("returnData is not good format");
}
}
json.keySet().stream().forEach(t1 -> {
if (!"opContent".equals(t1) && !"returnData".equals(t1)){
all.put(t1,json.get(t1));
}
});
return all;
}
}
写了那么多感觉好累喔,突然很想分成俩章写完 - . -
我是通过以下方式获取参数名称,然后根据参数封装成执行args,然后根据反射动态调用方法…
// 获取所有参数
ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
具体展示策略类的实现,不然总感觉看的兄弟会很懵逼
import com.alibaba.fastjson.JSONObject;
import com.hunt.shop.common.CommonUtil;
import com.hunt.shop.common.LogStrategyTask;
import com.hunt.shop.common.MethodMapping;
import com.hunt.shop.constant.Constant;
import com.hunt.shop.record.entity.UserVisitRecord;
import com.hunt.shop.record.service.business.impl.IRecordBusinessServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
/**
* @author xyang
* @version 1.0
* @date 2020/5/27 15:27
* @desc TODO
*/
@LogStrategyTask
public class UserVisitLogStrategyTask extends AbstractLogStrategyTask {
private static final Logger LOG = LoggerFactory.getLogger(IRecordBusinessServiceImpl.class);
@Autowired
protected IRecordBusinessServiceImpl irecordBusinessService;
@MethodMapping(Constant.WXDO_LOGIN)
public boolean task(JSONObject json, String opContent, String returnData) {
// 根据出参封装商城访问记录
UserVisitRecord userVisit = CommonUtil.userVisitRecordInfo(returnData);
Boolean result = irecordBusinessService.addUserVisitRecord(userVisit);
if (!result){
LOG.info("商城用户访问记录添加失败:addUserVisitRecord=>" + returnData);
}
return result;
}
}
此处就解决了一个类中一个方法的尴尬现状,但是代码很操蛋.同样的查询逻辑执行了俩边,总感觉效率会特别低…
为此,我特意翻阅了spring mvc的源码,看完感触良多,因此,我发现了一些问题.以下是我独特的见解.纯属个人观点…
我们既然谈到了mvc,就顺便简单聊聊mvc的调用
mvc的五大核心如下
HandlerMapping:处理器映射器
作用:根据【请求】找到【处理器Handler】,但并不是简单的返回处理器,而是
将处理器和拦截器封装,形成一个处理器执行链(HandlerExecuteChain)。
我主要借鉴了spring mvc 的handleMapping模块,这里大致讲下就过了.文章差不多也到了尾声
这里代码跳动的浮动会很大,甚至到了监听器的层次,敲重点,看的迷惑的铁汁能私信哥们解惑…
这里特别提出几个对象…
全局分为俩个流程
说白了,核心就是proxyTask,拿到了这个对象就可以很轻易的调用方法,下面是几个对象的代码
ProxyTask
import com.alibaba.fastjson.JSONObject;
import com.hunt.shop.common.CommonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author xyang
* @version 1.0
* @date 2020/6/2 16:05
* @desc TODO
*/
public class ProxyTask {
private static final Logger LOG = LoggerFactory.getLogger(ProxyTask.class);
/**
* proxy method
*/
private ProxyMethod proxyMethod;
/**
* handles
*/
private Object handles;
public boolean doWord(JSONObject json) throws InvocationTargetException, IllegalAccessException {
return proxyMethod.invoke(handles,json);
}
/**
* 构造
* @param method
* @param handles
*/
public ProxyTask(Method method,Object handles){
this.handles = handles;
initMethods(method);
}
private void initMethods(Method method){
// 获取所有参数
this.proxyMethod = new ProxyMethod(method);
}
private class ProxyMethod{
private Method method;
private String[] parameters;
public String[] getParameters(){
return parameters;
}
public ProxyMethod(Method method){
// 获取参数名称
ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
this.parameters = discoverer.getParameterNames(method);
this.method = method;
}
/**
* end invoke
* @param handle spring proxy task
* @param args methods mapping params
* @return result
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public boolean invoke(Object handle,Object[] args) throws InvocationTargetException, IllegalAccessException {
return (boolean) this.method.invoke(handle,args);
}
/**
* end invoke
* @param handle spring proxy task
* @param json methods json params
* @return result
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public boolean invoke(Object handle,JSONObject json) throws InvocationTargetException, IllegalAccessException {
LOG.info("执行测试方法....");
Object[] args = CommonUtil.sealedParam(parameters, json, method);
//return false;
return (boolean) this.method.invoke(handle,args);
}
}
}
MessageApplicationContent
该对象会在初始化的时候被注册到applicationContent中去,key为: “MessageApplicationContent”+".ROOT"
/**
* @author xyang
* @version 1.0
* @date 2020/6/2 17:43
* @desc TODO
*/
public class MessageApplicationContent {
public final Map<Integer,ProxyTask> mapping = new HashMap<Integer,ProxyTask>();
public ProxyTask getTask(int id){
return mapping.get(id);
}
}
ShopLogStrategyListener
// 需要注册监听器,从而达到初始化的作用
<listener>
<listener-class>com.hunt.shop.factory.ShopLogStrategyListener</listener-class>
</listener>
/**
* @author xyang
* @version 1.0
* @date 2020/6/2 15:52
* @desc TODO
*/
public class ShopLogStrategyListener implements ServletContextListener {
private static final Logger LOG = LoggerFactory.getLogger(ShopLogStrategyListener.class);
/**
* @Description: 初始化handles容器
* @Author: xyang
* @Date: 2020/6/2
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
LOG.info("----------------项目初始化------------------");
final ServletContext servletContext = sce.getServletContext();
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
// msg content
MessageApplicationContent mC = new MessageApplicationContent();
Map<String, Object> handles = context.getBeansWithAnnotation(LogStrategyTask.class);
handles.entrySet().forEach((map) -> {
Object handle = map.getValue();
// log
LOG.info("-----------init handle :"+map.getKey()+"----------");
//生成方法代理对象MethodHandle
Method[] methods = handle.getClass().getMethods();
for (Method method : methods) {
Integer requireId = Optional.ofNullable(AnnoUtils.getMethodAnnoValue(method, MethodMapping.class)).orElse(-99);
// 不缓存方法
if (requireId.equals(-99)){
continue;
}
LOG.info("-----------init handle METHOD:"+method.getName()+"----------");
ProxyTask proxyTask = new ProxyTask(method,handle);
mC.mapping.put(requireId,proxyTask);
LOG.info("-----------init handle METHOD:"+method.getName()+",SUCCESS-------------");
}
LOG.info("-----------init handle :"+map.getKey()+",SUCCESS----------");
});
// 存入application 中
//AutowireCapableBeanFactory autowireCapableBeanFactory = context.getAutowireCapableBeanFactory();
////autowireCapableBeanFactory.autowireBean(mC);
//autowireCapableBeanFactory.autowire(MessageApplicationContent.class,AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, true);
servletContext.setAttribute("MessageApplicationContent"+".ROOT",mC);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
工厂类代码如下
import com.hunt.shop.common.LogStrategyTask;
import com.hunt.shop.common.MethodMapping;
import com.hunt.shop.common.ShopLogUtils;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;
/**
* @author xyang
* @version 1.0
* @date 2020/5/27 14:53
* @desc 任务工厂,容器管理,别名转换,单例
*/
@Component
public class ShopLogStrategyHolder {
/**
* @Description: 从上下文中获取handle对象
* @Author: xyang
* @Date: 2020/6/2
*/
public ProxyTask getShopStrategyMethod(int requireId){
//获取handle对象
return ShopLogUtils.getMsgApplicationContent().getTask(requireId);
}
}
ShopLogUtils对象如下
/**
* @author xyang
* @version 1.0
* @date 2020/6/2 19:16
* @desc TODO
*/
@Component
@Lazy(false)
public class ShopLogUtils implements ServletContextAware {
/**
* 全局上下文对象
*/
public static ServletContext servletContext = null;
@Override
public void setServletContext(ServletContext servletContext) {
ShopLogUtils.servletContext = servletContext;
}
/**
* 获取当前上下文对象
* @return obj
*/
public synchronized static MessageApplicationContent getMsgApplicationContent(){
return (MessageApplicationContent)servletContext.getAttribute("MessageApplicationContent"+".ROOT");
}
}
策略类业务逻辑如下
@LogStrategyTask
public class StoreOperateLogStrategyTask {
private static final Logger LOG = LoggerFactory.getLogger(StoreOperateLogStrategyTask.class);
@Autowired
private IStoreOperateBusinessServiceImpl iStoreOperateBusinessService;
@Autowired
private IAdminProductBusinessServiceImpl iAdminProductBusinessService;
/**
* 添加店铺
* @param json
* @return
*/
@MethodMapping(Constant.ADD_STORE)
public boolean task(JSONObject json) {
String opContent=json.getString("opContent");
String returnData=json.getString("returnData");
StoreOperate storeOperate = CommonUtil.storeOperateInfo(opContent,Constant.ADD_PRODUCT);
//根据用户id判断操作用户
User user=iAdminProductBusinessService.queryUserByUserId(storeOperate.getOperateUserId());
if (!VerifyUtil.isEmpty(user)){
if (user.getWouldStore()==1){
storeOperate.setOperateSystem(Constant.OPERATE_SYSTEM_STORE);
}
}
boolean result =iStoreOperateBusinessService.addStoreOperateLog(storeOperate);
if (!result) {
LOG.info("店铺操作记录添加店铺失败:addStore=>" + storeOperate.getOperateUserId()+"店铺表添加是否完成"+returnData);
}
return result;
}
/**
* 店铺修改
* @param json
* @return
*/
@MethodMapping(Constant.UPDATE_STORE)
public boolean task1(JSONObject json) {
String opContent=json.getString("opContent");
String returnData=json.getString("returnData");
StoreOperate storeOperate = CommonUtil.storeOperateInfo(opContent,Constant.UPDATE_STORE);
//根据用户id判断操作用户
User user=iAdminProductBusinessService.queryUserByUserId(storeOperate.getOperateUserId());
if (!VerifyUtil.isEmpty(user)){
if (user.getWouldStore()==1){
storeOperate.setOperateSystem(Constant.OPERATE_SYSTEM_STORE);
}
}
boolean result =iStoreOperateBusinessService.addStoreOperateLog(storeOperate);
if (!result) {
LOG.info("店铺操作记录添加店铺失败:editStore=>" + storeOperate.getOperateUserId()+"店铺表添加是否完成"+returnData);
}
return result;
}
}
mian入口调用方法代码为
final ProxyTask shopStrategyMethod = shopLogStrategyHolder.getShopStrategyMethod(type);
shopStrategyMethod.doWord(json);
本文就到这里结束啦,码字累死了我丢,.