M2(核心控制器(DispatcherServlet))
1.小插曲:解决switch的效率过低
switch (operate) {
case "index":
index(request, response);
// 防止穿透!!!
break;
case "add":
add(request, response);
break;
case "del":
del(request, response);
break;
case "edit":
edit(request, response);
break;
case "update":
update(request, response);
break;
default:
throw new RuntimeException("operate参数非法!!!");
}
上面的switch写死了,要是添加了方法,又需要在分支中补充!!!
可以使用反射机制!!!
A.错误示例:使用方法名和形式参数列表来区分方法
// 通过反射机制调用当前类中的方法
try {
getClass().getDeclaredMethod("index").invoke(request,response);
} catch (IllegalAccessException |InvocationTargetException |NoSuchMethodException e) {
e.printStackTrace();
}
看起来是正确的
却报错误:
NoSuchMethodException:找不到方法
我们先循环当前类中的所有方法:
Method[] methods = getClass().getDeclaredMethods();
for (Method m : methods) {
System.out.println(m);
}
public void servlets.FruitServlet.index(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException
private void servlets.FruitServlet.add(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws javax.servlet.ServletException,java.io.IOException
private void servlets.FruitServlet.update(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException
protected void servlets.FruitServlet.service(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws javax.servlet.ServletException,java.io.IOException
private void servlets.FruitServlet.edit(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException
private void servlets.FruitServlet.del(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException
查询api
getDeclaredMethod(String name, Class<?>… parameterTypes) |
---|
返回一个 Method 对象, 该对象反映此 Class 对象所表示的类或接口的指定已声明方法 |
扩展:
为了区分方法重载,java区分方法是使用方法名和形式参数列表来区分的。
因此,需要传入方法名和形式参数参数列表的可变长度参数。
哦,找不到是因为getDeclaredMethod没有添加参数列表,太久没用反射,忘光了…
B.错误示例:可变长度参数的类型是Class
将上面的形式参数列表拷贝过来,直接使用
Method[] methods = getClass().getDeclaredMethod("index",javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse);
找不到符号…
扩展:
getDeclaredMethod()方法它的可变长度参数的类型是Class,
因此需要取到这个类的字节码,使用:任何类型.class
将包名省略之后,即:
try {
getClass().getDeclaredMethod("index", HttpServletRequest.class, HttpServletResponse.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
不报错了
C.错误演示3:invoke()方法需要实例化类
try {
Method method = getClass().getDeclaredMethod("index", HttpServletRequest.class, HttpServletResponse.class);
method.invoke(request,response);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
java.lang.IllegalArgumentException: object is not an instance of declaring class
......
at servlets.FruitServlet.service(FruitServlet.java:38)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
......
Object invoke(Object obj, Object… args) |
---|
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。 |
实际上第一个参数应该是Object,表示指定参数的指定对象!!!
使用当前对象this,调用方法!!!
D.代码
// 通过反射机制调用当前类中的方法
try {
Method method = getClass().getDeclaredMethod(operate, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this,request,response);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
2.介绍
每一个请求都必须通过Controller处理,然而其中有些请求是不需要模型和视图的
控制器是MVC应用程序的指挥员,负责编排用户,模型对象和视图交互,
同时还负责响应用户输入,操作正确的模型对象,选择合适的视图显示给用户
3.编写控制器
A.重写service方法
public class DispatcherServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}
B.设置注释
// 只要是以.do结尾的,将通过该控制器的service方法,*表示通配符
@WebServlet("*.do")
注意:不要以“/”开头!!!
效果:
B.获取请求的地址字符串
// 设置编码
request.setCharacterEncoding("utf-8");
// 获取当前请求的地址的字符串
String servletPath = request.getServletPath();// 获得 /XXXX.do
System.out.println(servletPath.substring(1,servletPath.length()-3)); // 获得 XXXX
C.创建applicationContext.xml文件
介绍:Spring中的容器,可以用来获取容器中的各种bean组件,注册监听事件,加载资源文件等功能
- 组件改名为Controller
编写XML文件
通过该XML文件,通过id就可以获得其对应Controller的所在的位置!!!
顺口溜:一袋饼(beans)里有多个饼(bean)
<?xml version="1.0" encoding="UTF-8"?>
<!-- xml 声明version 是版本的意思encoding 是编码-->
<beans>
<!-- 不以/开头,不带后缀名,路径使用"."!!!-->
<bean id="fruit" class="controllers.FruitController"/>
</beans>
C.重写init方法(解析XML放入HashMap中)
我这里使用dom4j
路径问题(getResource() 方法)
dom4j需要使用绝对路径,为什么?先看下面的代码示例
read = reader.read("src/applicationContext.xml");
报找不到文件错误
这个路径是十分奇怪的,它不从IDEA的OUT中寻找,而是从Tomcat的目录去寻找
因此,我们需要获得当前java文件的路径,通过该路径去获取配置文件
这样用,不管什么在哪里,都可以获取配置文件
方法 | 作用 |
---|---|
getClass() | 取得当前对象所属的Class对象 |
getClassLoader() | 取得该Class对象的类装载器 |
getResource() | 得到文件路径的函数 |
getPath() | 将此抽象路径名转换为一个路径名字符串 |
getClass().getClassLoader().getResource("applicationContext.xml").getPath()
效果:
解析XML文件
步骤 | 方法 |
---|---|
1 | 创建解析器(SAXReader) |
2 | 通过解析器将文件解析为 Document |
3 | 通过 document 对象获取根元素(Root Element) |
4 | 通过根元素(Root Element)获取所有的子元素(Element) |
// 1.获取解析器
SAXReader reader = new SAXReader();
// 2.通过解析器将文件解析为 Document
Document read = null;
try {
read = reader.read(getClass().getClassLoader().getResource("applicationContext.xml").getPath());
} catch (DocumentException e) {
e.printStackTrace();
}
// 3.通过document对象获取根元素(Root Element)
Element rootElement = read.getRootElement();
// 4.通过根元素(Root Element)获取所有的子元素(Element)
// 获取beans下的所有bean
List<Element> list = rootElement.elements("bean");
创建beanClassNameMap常量,放入数据
创建常量:
private static HashMap<String,Object> beanClassNameMap = new HashMap<>();
可以通过Class的newInstance()方法来实例化对象
newInstance()这个方法会调用类的无参数构造方法,完成对象的创建
注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
// 取出元素,放入beanMap
for (Element e : list) {
// 获取id
String id = e.attributeValue("id");
// 获取路径
String aClass = e.attributeValue("class");
// 将路径转换为对象
Object forName = null;
try {
forName = Class.forName(aClass).newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
ex.printStackTrace();
}
// 放入Map中
beanClassNameMap.put(id, forName);
}
效果:
D.使用service方法(调用请求地址对应的方法)
通过地址的字符串获取对应的controller层
// 获取地址的字符串获取对应的controller层
Object servletPathObj = beanClassNameMap.get(servletPath);
// 安全控制,如果是非法的,默认设置fruit(默认跳转到水果业务上)
if (servletPathObj == null) {
servletPathObj = beanClassNameMap.get("fruit");
}
根据其请求的携带的数据,反射调用不同的方法响应
报错:私有方法!!!
可以使用setAccessible()方法来打破封装
错误示例代码(后面解决)
// 获取请求携带的参数
String operate = "index";
// 没有携带参数,默认index
if (!(Util.isEmpty(request.getParameter("operate")))) {
operate = request.getParameter("operate");
}
// 根据参数调用不同的方法
try {
Method method = servletPathObj.getClass().getDeclaredMethod(operate, HttpServletRequest.class, HttpServletResponse.class);
// 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会!!!)
// 这样设置完之后,在外部也是可以访问private的。
method.setAccessible(true);
method.invoke(this,request,response);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
使用DeBug解决视图问题(看下一节)
删除冗余代码
删除controller类中的service方法
4.效果(完整DeBug演示)
5.扩展
getResource() 方法的注意事项
Class.getResource(String path)
path不以’/'开头时,默认是从此类所在的包下取资源;
path以’/‘开头时,则是从项目的ClassPath根下获取资源。在这里’/'表示ClassPath的根目录。