尚硅谷JavaWeb-核心控制器思想-M2(核心控制器(DispatcherServlet))

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的根目录。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值