这一篇博客的主要内容是我跟着尚硅谷敲的一个简单的项目。主要目的就是自己梳理一遍,加深记忆。
项目的结构
- src
- fruit
- controllers
- dao
- pojo
- myssm
- basedao
- myspringmvc
- fruit
其中,dao、pojo、basedao里面是有关JDBC的代码,不是重点。
重点是下面几个类:
- controllers
- FruitContriller
- myspringvc
- DispatcherServlet
- ViewBaseServlet
还有:applicationContext.xml和web.xml这两个配置文件
具体实现
ViewBaseServlet
这一个是继承与HttpServlet的一个类,之后我们的servlet去继承ViewBaseServlet的类的目的是用里面的一个方法。
这其实是在使用Thymeleaf视图模块技术。
而我们这样做的目的是使用ViewBaseServlet中的一个方法:
这个方法有三个参数:
1.templateName
2.req
3.resp
此处templateName是视图名称
req和resp对应的就是view-prefix和view-suff
而view-prefix和view-suff是在web.xml文件,被成为前缀和后缀。
其中里面的
<param-value>value</param-value>
中的value的值就是后面会替换的值
即上面的方法调用后会得到下面的值:
thymeleaf会将这个 逻辑视图的名称 对应到 物理视图 名称上去
逻辑视图名称 :templateName
物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
所以真是的视图名称是 : / templateName.html
DispatcherServlet
这个类是这个项目的核心类。
首先,我们思考一个问题,如果我们为每一个请求都设置一个Servlet,那么我们在一个项目里面要写多少个Servlet,里面又有多少重复冗余的代码。因此,我们有必要创建一个类,来统合并且将请求分配给对应的Servlet。
这一个类的主要思想就是利用了反射技术,来获取各种信息,并分配到对应的类中处理。
1)第一步:
我们需要根据收到的请求来分配到对应的Servlet。
这里我们需要用到request.getServletPath()
方法
具体思想如下
假设url时:http://localhost:8080/08_fruit2_0_mvc_dispatcherServlet/hly.do
那么servletPath是: /hly.do
思路是:
第一步:/hly.do -> hly
第二步: hly -> hlyController
代码如下:
String servletPath = req.getServletPath();
servletPath = servletPath.substring(1);
int lastDotIndex = servletPath.lastIndexOf(".do");
servletPath = servletPath.substring(0,lastDotIndex);
此时,我们就可以获得一个 fruit。之后,我们就需要根据fruit来寻找对应的Servlet,如何来实现这一个功能呢?
2)第二步
此时,就需要看我们写的applicationContext.xml文件
此时就将fruit和com.vector.fruit.controllers.FruitController联系到一起。
所以,我们需要加载一下applicationContext.xml配置文件
我们在init()方法中将这两个值存储在HashMap集合中
完整代码如下:
private Map<String,Object> beanMap = new HashMap<>();
public void init() throws ServletException {
super.init();
try {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
//1.创建DocumentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//2.创建DocumentBuilder对象
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//3.创建Document对象
Document document = documentBuilder.parse(inputStream);
//4.获取所有的bean节点
NodeList beanNodeList = document.getElementsByTagName("bean");
for (int i = 0; i<beanNodeList.getLength(); i++){
Node beanNode = beanNodeList.item(i);
if (beanNode.getNodeType() == Node.ELEMENT_NODE){
Element beanElement = (Element) beanNode;
String beanid = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
Class controllerBeanClass = Class.forName(className);
Object beanObj = controllerBeanClass.newInstance();
beanMap.put(beanid,beanObj);
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
}
逐一分析:
我们首先需要获取配置文件的信息
我们使用Document对象获取applicationContext.xml的文件信息。之后,获取配置文件中的所有bean节点。
NodeList beanNodeList = document.getElementsByTagName("bean");
然后,利用循环,将所有bean节点的信息存储进Map中。
可以看到,beanid=“fruit”,beanObj=“FruitConttoller@3217”。
在Map中也成功存入。然后,就可以进行下一步操作。
3)第三步
我们创建一个service方法,将第一步的代码放入,然后我们利用第一步获得的fruit来获得Map中对应的类名
Object controllerBeanObj = beanMap.get(servletPath);
接下来看下面的代码
String operate = req.getParameter("operate");
if (StringUtil.isEmpty(operate)){
operate = "index";
}
根据html的代码,可以知道,operate是各种操作的name。所以,当operate为空时,我们是要进入默认页面(“index.html”)
4)第四步
在判断用过使用那个Servlet后,根据用户的请求来安排对应的方法。
Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();
根据反射来获取类的所有方法,然后循环判断使用那个方法
for (Method method : methods){
if (operate.equals(method.getName())) {
.......
}
之后,获取当前方法的参数,返回参数数组。(使用getParameters())
Parameter[] parameters = method.getParameters();
定义一个parameterValues 用来承载参数的值
Object[] parameterValues = new Object[parameters.length];
for(int i = 0; i < parameters.length; i++){
Parameter parameter = parameters[i];
String parameterName = parameter.getName();
......
}
然后,判断是否是通过请求来获取的参数(即参数名不是req,resp,session)
if ("req".equals(parameterName)){
parameterValues[i] = req;
}else if ("resp".equals(parameterName)){
parameterValues[i] = resp;
}else if ("session".equals(parameterName)){
parameterValues[i] = req.getSession();
}else{
.......
}
到此,判断调用那个方法结束
5)第五步
我们要从请求中获取参数的值和类型
String parameterValue = req.getParameter(parameter.getName());
String typeName = parameter.getType().getName();
获取类型的原因是,我们通过反射获得的参数值的类型都是String类型,但参数实际上会存在其他类型。所以,我们知道它的类型然后进行转型。如下:
Object parameterObj = parameterValue;
if (parameterObj != null){
if ("java.lang.Integer".equals(typeName)){
parameterObj = Integer.parseInt(parameterValue);
}
}
最后将结果放入parameterValues[]数组中。
parameterValues[i] = parameterObj;
6)第六步
在controller(Servlet)组件中的方法调用,找到和operate同名的方法,通过反射技术调用它
method.setAccessible(true);
Object returnObj = method.invoke(controllerBeanObj,parameterValues);
最后,我们进行视图处理
String methodeReturnStr = (String) returnObj;
if (methodeReturnStr.startsWith("redirect:")){//比如:redirect:fruit.do
String redirectStr = methodeReturnStr.substring("redirect:".length());
resp.sendRedirect(redirectStr);
}else {
super.processTemplate(methodeReturnStr,req,resp); //比如:"edit"
}