分发器的简单应用

引入
在servlet中,当容器接收到前端发过来的request时,会通过解析操作来调用GenericServlet中的service(ServletRequset,ServletResponse)方法,当然我们是需要在Web.xml中配置的,那么就可以通过这个xml文件的配置找到这个类并且调用其中的方法,类的信息以及参数的信息通过http协议发送到服务器端。但一般是直接使用了httpServlet类来处理,因为更加的简单,但有一个弊端,就是每次请求和响应都需要一个类来处理,这太过麻烦,不如写个子类,我们增强一下类的功能,通过ognl来访问一个类里头的不同方法显得更加合理。
做法:反正我们知道了httpservlet能接受request和返回response,不如就直接增强httpservlet更加的方便,因为httpservlet已经可以达到我们的目的了,就不必使用GenericServlet类了。
上码:

public class BaseServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	@Override
	public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

		// localhost:8080/store/productServlet?method=addProduct 这是访问时的url ognl加上method的键值对告知调用哪个方法
		String method = req.getParameter("method");
		
        //如果这个method有问题,就调用execute方法
		if (null == method || "".equals(method) || method.trim().equals("")) {
			method = "execute";
		}

		//获取当前的类对象类型
		Class<? extends BaseServlet> clazz = this.getClass();
        try {
			Object object = clazz.newInstance();
			//根据method找方法,因为子类拥有父类的空间我们这个url是访问到子类去了,然后容器调用service方法,
			//就来这里了,这个method就是在找子类的方法,没有就reutn null,不影响
			Method md = clazz.getMethod(method, HttpServletRequest.class, HttpServletResponse.class);
			if(null!=md){
				String jspPath = (String) md.invoke(object, req, resp);
				if (null != jspPath) {
					//转发,因为servlet一般最后一部都是转发到另一个地方去,所以干脆放这个工具里头,不想转发return null就好了
					req.getRequestDispatcher(jspPath).forward(req, resp);
				}
			}
		} catch (InstantiationException e1) {
			e1.printStackTrace();
		} catch (IllegalAccessException e1) {
			e1.printStackTrace();
		}		 catch (Exception e) {
			e.printStackTrace();
		}

	}

	//万一翻车就什么不做,可以安全一点
	public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		return null;
	}
}

这里上app开发者需要使用的方法

 public class CartServlet extends BaseServlet{
	// localhost:8080/store/productServlet?method=addCartItemToCart
	public String addCartItemToCart(HttpServletRequest request, HttpServletResponse response) {
		
		return null;
	}
// localhost:8080/store/productServlet?method=deleteCartItem
	public String deleteCartItem(HttpServletRequest request, HttpServletResponse response)  {
		return "/jsp/cart.jsp";
	}
  // localhost:8080/store/productServlet?method=clearCart
	public String clearCart(HttpServletRequest request, HttpServletResponse response)  {
		return "/jsp/cart.jsp";
	}

}

这下只用在ognl中加method键值对就可以在一个类中调用方法啦!
这块儿的反射机制小编自己觉得比较难以理解,反射和继承混在一起用了,才能达到这种效果,其实可以仔细分析一下,子类包括父类的资源,只是多了一个指针指向父类的空间了,一个指针指向子类空间,这这里把父类代码抽象到子类里也是可以的,url发到httpservlet这且调用service我们已经知道了,就是不直接做事了,给未来的子类方法做了,这提高了代码复用性,也算是工具开发了嘛,更加的像java了。

小小分发器
刚刚我们写的小工具有点分发器的影子,然而这并不是分发器,缺少通用性,那么分发器是做什么的呢,其实功能也很像,就是通过ognl判断该哪个类的哪个方法调用,并且通过ognl把参数写好,来调用一下这个方法。tomcat就运用了这个机制,也就是我们刚刚为什么那个httpservlet下的service会调用它就是这个机制,struct2就是通过xml配置来实现核心功能的,只不过不像我们实现的这么垃圾,这么没技巧,不过思想是相同的,通过上面的代码我们可以稍稍的了解一下思想。
开始新一轮的理解了,xml配置也是可以的而且可以不修改源代码,但利用注解可以更加明晰的看到分发器的用途,利用注解来实现分发器的简单功能。

思想:
1.我们给类加上注解,那么就可以通过包扫描得到加注解的类,我们就简单的封装在一个map中吧,键是注解的string,值就是方法啦。

2.深入的想一下,我们直接用method合适吗?答案是否定的,因为method的方法参数在使用invoke时必须按照method的顺序来,但是ognl可不见得就是这样的了,毕竟给别人用的,我们不能限制用户严格要求格式,这不是一个合格开发者的做法,而且后台接收到的一般是json数据,这就很难保证这一点。

预期效果:通过注解名和要调用函数参数的json来调用方法。
用户操作

通过这一句达到调用下述方法的效果。
解释一下,ArgumentsMaker是一个的工具类,就是把键值对先变成Map,在把这个map变成json。

 String res =  (String) iAction.dealrequest("getStudentById", new ArgumentsMaker()
			.addArgument("student", studentModel)
			.addArgument("age", "158")
			.addArgument("id", "123456")
			.toGson()
			);`
			这就是转化的json
			{"student":"{\"sex\":false}","id":"\"123456\"","age":"\"158\""}

要操作的方法是getStudentById因为他加了ActionMethod注解,这三个注解太简单了,小编就不给代码了,一个对类,一个对方法,一个对参数,有人要问了,为啥要有类注解啊,你想啊,包扫描出来的都是类,我们可以通过类快速筛选出需要的类,只有带注解的类才有会被调用的方法,这样程序就快了。万一你导了许许多多的jar包,每个类的每个方法都检查一下,这不就降低了效率了嘛。

@MecAction
public class StudentAction {

	@ActionMethod(action="getStudentById")
	public StudentModel getStudentById(
			@ActionParameter(name="id") String id, 
			@ActionParameter(name="student") StudentModel student) {
		System.out.println("id:" + id + ", student:" + student);
		
		return student;
	}
	
}

怎么做呢,先写个类吧,把方法封装一下

public class ActionDefination {
	//可能要用,先放着
	private Class<?> klass;
	//最重要的自然是方法啦
	private Method method;
	//这是方法的多个参数
	private List<Parameter> parameterlist;
	//同第一
	private Object object;
	
	
	public ActionDefination(Class<?> klass, Method method, List<Parameter> parameterlist, Object object) {
		this.klass = klass;
		this.method = method;
		this.parameterlist = parameterlist;
		this.object = object;
	}
	public Class<?> getKlass() {
		return klass;
	}
	public void setKlass(Class<?> klass) {
		this.klass = klass;
	}
	public Method getMethod() {
		return method;
	}
	public void setMethod(Method method) {
		this.method = method;
	}
	public List<Parameter> getParameterlist() {
		return parameterlist;
	}
	public void setParameterlist(List<Parameter> parameterlist) {
		this.parameterlist = parameterlist;
	}
	public Object getObject() {
		return object;
	}
	public void setObject(Object object) {
		this.object = object;
	}
}

这里有点奇怪啊,有klass为啥还要paramterlist?用method不就得到数组了吗?都可以,我这在模型中直接转化成list了,这样好用一点。
模型有了,我们接下来就是生成注解字符串和方法对象(ActionDefination)之间的映射关系了,等一下,是映射嘛?我分发器要通过一个注解调用多个方法怎么办?行啊,map的值变成list就好了啊,小编怕大家看的模糊,所以就考虑一对一了,要修改也是很容易的嘛。
工厂类上!

public class ActionFactory {
	//我们通过包扫描可以生成一个map(factory的目的!)这个map完成注解和方法抽象对象的映射关系
	private static final Map<String, ActionDefination> actionmap = new HashMap<>();
	
	//毕竟是个map,增删查还是要有的,万一以后要用呢?
	public static void addActionDefination(String action,ActionDefination actionDefination) {
		if(actionmap.containsKey(action)) {
			return;
		}
		actionmap.put(action, actionDefination);
	}
	public static ActionDefination getActionDefination(String key) {
		return actionmap.get(key);
	}
	public static void removeActionDefination(String key) {
		if(!actionmap.containsKey(key)) {
			return;
		}
		actionmap.remove(key);
	}
	//通过调用这个方法来实现工厂的初始化
	public static void scanAction(Class<?> klass) {
		scanAction(klass.getPackage().getName());
	}
	//重载函数,通过包扫描完成map的构建
	public static void scanAction(String packageName) {
		new PackageScanner() {
			@Override
			public void dealClass(Class<?> klass) {
				if(klass.isAnnotationPresent(MecAction.class)) {
					Object object=null;
					try {
						object = klass.newInstance();
					} catch (InstantiationException | IllegalAccessException e) {
						e.printStackTrace();
					}
					//带我们注解的类的class都扫描出来了,有class了,开始生成我们需要的map吧
					scanMethod(klass,object);
				}
					
			}
		}.packageScan(packageName);;
	}
	//根据class和object来生成map里的entry
	private static void scanMethod(Class<?> klass,Object object) {
		Method[] declaredMethods = klass.getDeclaredMethods();
		for(Method method:declaredMethods) {
			if(method.isAnnotationPresent(ActionMethod.class)) {
				//把带注解的方法都扫描出来
				ActionMethod annotation = method.getAnnotation(ActionMethod.class);
				String action = annotation.action();
				List<Parameter> parameterlist = new ArrayList<>();
				//其实这个parmeter毫无卵用,都是些arg0,arg1,为什么要?是为了获取上面的注解
			    for(Parameter parameter : method.getParameters()) {
			    	parameterlist.add(parameter);
			    }
				ActionDefination ad = new ActionDefination(klass, method, parameterlist, object);
				ActionFactory.addActionDefination(action, ad);
			}
		}
	}
}

包扫描的博客太多了,小编就不赘述了,好啦,我们这就把注解和方法映射起来啦,有了这个map不就可以为所欲为了嘛。

接下来就是最后一步了,根据传过来的注解和参数完成程序调用,map都有了,直接上!

public class Action implements IAction{
     private static Type type;
     private static Gson gson;
     static {
    	 //这一句是咒语,意思就是让你的Gson把json字符串可以变成Map<String,String>的对象的,下面有用到
    	 type = new TypeToken<Map<String, String>>(){}.getType();
    	 gson= new GsonBuilder().create();
     }
	//通过action在map里找到ActionDefination完成参数的调用实现分发(就是能调用一下方法)
     //这里特别说一下下这个paramterlist,这是map形成的json,这个map键是参数名,value是参数值,json出来是个字符串.
	@Override
	public String dealrequest(String action, String paramterlist) {
		ActionDefination ad = ActionFactory.getActionDefination(action);
		Object object = ad.getObject();
		Method method = ad.getMethod();
		List<Parameter> parameterlist = ad.getParameterlist();	
		Object result=null;
		try {
		//把json转换成list对象
			Object[] paramterobjectlist = dealParamter(parameterlist,paramterlist);
			result = method.invoke(object,paramterobjectlist);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoAnnotionException e) {
			e.printStackTrace();
		}
		System.out.println(method);
		return gson.toJson(result);
	}
    //把json转换成Object[]就大功告成了!but!你有没有发现map是没有顺序的,而invoke的参数需要严格
	//要求顺序!所以我们的注解就有作用了
	private Object[] dealParamter(List<Parameter> paramlist,String paramterlist) throws NoAnnotionException {
		int i=0;
		Object[] result = new Object[paramlist.size()];
		if(paramterlist==null) {
			return null;
		}
		//前面用的咒语君起作用了,paramterlist变成map,key是参数的键,value是参数的值
		Map<String, String> paramtermap = gson.fromJson(paramterlist,type);
		for(Parameter paramter:paramlist) {
			if(!paramter.isAnnotationPresent(ActionParameter.class)) {
				//TODO 抛出异常
				throw new NoAnnotionException("这个方法有一个参数没有加注解,检测不到,无法完成");
			}
				ActionParameter annotation = paramter.getAnnotation(ActionParameter.class);
				//获得注解名,也就是参数名
				String annotationname = annotation.name();
				//根据参数名拿出      参数的参数(参数的值!)
				String value = paramtermap.get(annotationname);
				//json转对象大法好啊!value是简单的字符串,数字都没问题的
				Object Jsonobj = gson.fromJson(value, paramter.getParameterizedType());
				result[i++]=Jsonobj;
				
			}
		    		return result;
	}
}

这块比较难以理解的就是把json转换成和方法参数顺序相同的object数组了,小编说一下我们首先用下json工具,把json变成map<string,string>我们的工具类需要一个type,这不是小编写的,所以照人家的用吧这个类是Gson可以查一下。
json变成了键值对map,键是参数名,值是参数值的String
然后根据注解从map查出来我们的方法,就是那个方法模型类,里头有paramterlist,这个paramterlist的顺序和方法参数的顺序是一样的,因为klass.getparamters()获得的就是有序的。
根据这个注解获取这个方法的各个参数的名字(为什么?上面解释了method只能获得arg0,arg1这种毫无卵用的名字)那是不是意味着我们的参数注解只能写参数名了呢?是啊,你要不写一样的也行,但得保证你的注解和参数保持映射,xml就不错,但是我觉得遵守一下秩序就好,配置xml也是麻烦事,不是吗?
根据json大法把参数字符串转换成对象转化成什么对象?paramter里头是有的。
形成参数数组
好啦,这样就能把无序的ognl变成有序的参数对象啦!我们的解说也到了尾声。

这东西相当简陋,对用户的要求也很苛刻,我们如何把ognl变成map还没有完成,因为我们不针对http,而是tcp层,协议可能是有些程序员自己定义的,因此解析json称为map数组对象还需开发人员自己解决。
最后,这代码没卵用,学习思想然后自己根据实际需求修改才是王道!

public class Test {

	public static void main(String[] args) {
		Gson gson =new GsonBuilder().create();
		Type type = new TypeToken<Map<String,String>>(){}.getType();
		ActionFactory.scanAction("com.mec");
		IAction iAction = new Action();
	
	    Map<String, String> ognlmap = new HashMap<>();
	    ognlmap.put("age", "158");
	    ognlmap.put("id", "123456");
	    ognlmap.put("student", "{\"id\":\"12365\",\"name\":\"hzy\",\"sex\":true,\"introcude\":\"abce\"}");
	    
	
		//通过给注解和这个注解对应的方法,调用这个方法,参数是json
		String res =  (String) iAction.dealrequest("getStudentById", gson.toJson(ognlmap)
				);
		
		System.out.println("方法的返回值:"+res);
	}

}

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值