引入
在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);
}
}