不少同学在学习SSM框架的时候,无论是初学者还是工作过一段时间的同学,都会被其繁琐的配置搞得心烦意乱,或者一问起Spring最经典的两个核心思想,IOC以及AOP,可能也就仅仅知道其表示的表面意思,稍微呢,往深了一问,就显得比较力不从心了。还有咱们并不陌生的SpringMvc,我相信咱们大家几乎都知道它的底层封装了一个叫做DispatcherServlet的Servlet,但是中央控制器是怎么协同对应的Controller工作的,可能就一无所知了。
所以呢,为了让咱们更深一步的理解两者,在后面的课程或者面试更具有主动性,咱们手写一个简单的IOC容器以及DispatcherServlet中央控制器,一方面来呢,加强同学们对反射技术的理解与应用,另一方面呢,让咱们在学习过程中,能够保持一颗钻研的心,让咱们学习时不仅知其然,更能知其所以然。
在本次讲解过程前,需要同学们具备JavaSE的基础知识,同时也需对JavaWeb特别熟悉。
一、原始的Web案例
下面呢,我们通过咱们最熟悉的web技术完成一个学生管理系统:
1.1数据工具类
StudentUtils为咱们的数据源类,主要用于操作学生,提供了增删改查功能,因为咱们今天的重点不在数据库,所以提供一个工具类来简单模拟数据库dao层。
package com.ignorance.utils;
import com.ignorance.bean.Student;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author ignorance0916
* @descript 学生数据工具类,主要提供学生的增删改查方法
*/
public class StudentUtils {
private static final Map<Integer,Student> STUDENT_MAP;
static {
STUDENT_MAP = new HashMap<>();
Student s1 = new Student(1001,"周芷若",18,"女","计算机科学与技术",120F);
Student s2 = new Student(1002,"张无忌",22,"男","软件工程",148f);
Student s3 = new Student(1003,"黄蓉",21,"女","经济管理",141f);
Student s4 = new Student(1004,"夏诗涵",19,"女","计算机科学与技术",137F);
Student s5 = new Student(1005,"欧阳锋",26,"男","土木工程",87F);
Student s6 = new Student(1006,"郭靖",23,"男","数学",99F);
STUDENT_MAP.put(s1.getNo(),s1);
STUDENT_MAP.put(s2.getNo(),s2);
STUDENT_MAP.put(s3.getNo(),s3);
STUDENT_MAP.put(s4.getNo(),s4);
STUDENT_MAP.put(s5.getNo(),s5);
STUDENT_MAP.put(s6.getNo(),s6);
}
public static List<Student> getData(){
return STUDENT_MAP.values().stream().collect(Collectors.toList());
}
public static void remove(Integer no){
STUDENT_MAP.remove(no);
}
public static Student getStudentByNo(Integer no){
return STUDENT_MAP.get(no);
}
public static void modify(Student student){
boolean flag = false;
if (!STUDENT_MAP.containsKey(student.getNo())){
return;
}
STUDENT_MAP.put(student.getNo(),student);
}
public static List<Student> findByName(Student student){
return STUDENT_MAP.values().stream().filter(e -> e.getName().equals(student.getName())).collect(Collectors.toList());
}
}
1.2学生查询功能
最原始的web技术我想就不用多说了,我们创建一个Servlet,将前后端绑定起来,不知道你是否还有印象,比如咱们完成一个查询功能,需要进行如下操作:
1.2.1 创建Servlet实现类
package com.ignorance.servlet;
import com.ignorance.bean.Student;
import com.ignorance.service.StudentService;
import com.ignorance.service.impl.StudentServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* @author ignorance0916
* @descript 学生查询功能Servlet
*/
public class StudentQueryAllServlet extends ViewBaseServlet {
private StudentService studentService = new StudentServiceImpl();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<Student> list = studentService.findAll();
req.setAttribute("list",list);
super.processTemplate("index",req,resp);
}
}
我们创建StudentQueryAllServlet来处理客户端的查询请求,那么怎么将查询请求和该Servlet进行绑定呢?可不是你创建了就能处理你指定的请求,下一步,我们需要在咱们web的核心配置文件web.xml去映射咱们的客户端请求。
1.2.2 配置web.xml
<servlet>
<servlet-name>StudentQueryServlet</servlet-name>
<servlet-class>com.ignorance.servlet.StudentQueryAllServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>StudentQueryServlet</servlet-name>
<url-pattern>/index</url-pattern>
</servlet-mapping>
这段配置我相信学过web的同学们,应该是再熟悉不过了。当客户端发送index请求时,服务器会解析web.xml,解析的步骤如下所示:
1.会查找<servlet-mapping>标签,匹配其子标签<url-pattern>的值是否为index;
2.找到对应的<servlet-mapping>标签后,得到其子标签<servlet-name>的值;
3.通过<servlet-mapping>的<servlet-name>的值匹配<servlet>标签中子标签<servlet-name>;
4.匹配后,可以得到其<servlet-class>的值,也就是咱们后台Servlet的全类名;
5.拿到类路径后,熟悉反射的同学们应该就能立马想到,此时底层会通过该路径创建出对应的Servlet对象,并调用其service()方法。
这些文字可能有点绕,咱们用下面的图来表示上述的整个过程:
1.2.3 业务层接口
package com.ignorance.service;
import com.ignorance.bean.Student;
import java.util.List;
/**
* @author ignorance0916
* @descript 学生业务类接口
*/
public interface StudentService {
List<Student> findAll();
}
1.2.4 业务层接口实现类
package com.ignorance.service.impl;
import com.ignorance.bean.Student;
import com.ignorance.service.StudentService;
import com.ignorance.utils.StudentUtils;
import java.util.List;
/**
* @author ignorance0916
* @descript 学生业务类接口实现类
*/
public class StudentServiceImpl implements StudentService {
@Override
public List<Student> findAll() {
return StudentUtils.getData();
}
}
1.2.5 运行结果
1.3学生删除功能
1.3.1 创建删除功能Servlet
package com.ignorance.servlet;
import com.ignorance.service.StudentService;
import com.ignorance.service.impl.StudentServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author ignorance0916
* @descript 学生删除功能Servlet
*/
public class StudentDelServlet extends ViewBaseServlet {
private StudentService studentService = new StudentServiceImpl();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String no = req.getParameter("no");
studentService.del(Integer.parseInt(no));
resp.sendRedirect("index");
}
}
1.3.2 配置web.xml
<servlet>
<servlet-name>DelServlet</servlet-name>
<servlet-class>com.ignorance.servlet.StudentDelServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DelServlet</servlet-name>
<url-pattern>/del</url-pattern>
</servlet-mapping>
1.3.3 业务层接口
package com.ignorance.service;
import com.ignorance.bean.Student;
import java.util.List;
/**
* @author ignorance0916
* @descript 学生业务类接口
*/
public interface StudentService {
List<Student> findAll();
void del(Integer no);
}
1.3.4 业务层接口实现类
package com.ignorance.service.impl;
import com.ignorance.bean.Student;
import com.ignorance.service.StudentService;
import com.ignorance.utils.StudentUtils;
import java.util.List;
/**
* @author ignorance0916
* @descript 学生业务类接口实现类
*/
public class StudentServiceImpl implements StudentService {
@Override
public List<Student> findAll() {
return StudentUtils.getData();
}
@Override
public void del(Integer no) {
StudentUtils.remove(no);
}
}
1.3.5 运行结果
二、原始架构弊端
上面的代码几乎相差不大,后面的功能我们也不再一一列举,相信学过这些原始代码的同学都会有这么一些问题。
2.1藕合度太高
咱们的StudentQueryAllServlet完成对应的查询功能,需要紧密的依赖咱们的StudenService,同样的道理,StudentDelServlet调用后台获取数据同样需要依赖StudentService,包括后面的修改,新增等一系列操作,它们同样都需要依赖StudenService。试想如果,此时我将StudentService这个类名或者属性进行修改了,那么是不是以前依赖的逻辑全部都需要重写啊,比如:我将StudentService类名进行修改。
目前咱们只有两个引用,在咱们开发中,可能存在成千上万次的引用,而且被引用的类远远不止一个,出现这种问题是不是对咱们系统和开发人员的毁灭性打击。
2.2Servlet过多
咱们的客户端每次发送一个请求,后端都需要创建一个Servlet对象,并且同时重写service()方法。试想如果一个系统成千上万个请求,那是不是要创建成千上万个Servlet,会造成Servlet类的急剧暴增,对我们编码来说也是特别麻烦和讨厌的存在。
三、解决方案
针对以上的两个问题,我们给出如下对应的解决方案:
3.1IOC
3.1.1 概念
以前咱们创建对象用得是直接调用构造器的方式,这个是咱们最熟悉的方式,优势简单明了,不用拐弯抹角。就像你自己找媳妇儿一样,自己想找就找,想分就分,方便是方便,但是会存在很多讨厌的依赖关系;比如你约个网友奔现,结果发现只是照骗,你想退货,别人觉得你耽误她的青春,向你索要一大笔损失费,最后一直缠着你不放,你发现自己的一时方便往往给自己带来了更多的麻烦。还有就是咱们平时自己做饭,可以自己随心所欲,想做什么就做什么,后来有一次你发现自己做得菜太难吃了,这时候是不是又需要大费周章重新折腾一番?
IOC翻译成中文叫做控制反转,它的核心是将咱们Java创建对象的控制权上交给容器,它常常跟依赖注入搭配在一起,意思是在项目启动时,服务器就完成了咱们所有对象的创建,我们开发者就不需要再去new对象了,我们需要什么就直接从容器拿就可以了,就像做饭一样,自己做太麻烦,咱们就去饭店吃,点的东西不符合口味自己换就行。这时候饭店就扮演着IOC容器的作用,每份饭菜就相当于一个对象,也就是bean,每份饭菜的搭配就相当于饭菜的装配与依赖注入,例如青椒肉丝盖饭需要青椒、肉丝以及米饭三种材料,容器在初始化时创建青椒肉丝盖饭时就会将这三者组合在一起。
例子谁都能理解,但是一遇到代码就废,我相信这是大多数学习者最常见的问题,咱们结合一下咱们的代码分析一下:
比如完成查询功能忽略底层一共需要两个实例对象:一是咱们后端对应的控制器StudentQueryAllServlet,二是业务层StudentService。它们之间还包括相应的组合关系,StudentQueryAllServlet中需要依赖StudentService,它需要借助StudentService类的实例对象请求后台数据然后返回给客户端,所以说StudentQueryAllServlet要想真正完成,必须要初始化StudentService,不然你懂得,就是咱们频频遇到,对你眷顾很久的服务器500错误,让人抓狂的NullpointerException。我们说了这些对象的创建不能直接让咱们new了,这时候咱们怎么办了,请看咱们下面的分析。
3.1.2 实现
3.1.2.1 创建applicationContext.xml配置文件
<beans>
<bean id="studentService" class="com.ignorance.service.impl.StudentServiceImpl"></bean>
<bean id="student" class="com.ignorance.controller.StudentController">
<property name="studentService" ref="studentService"></property>
</bean>
</beans>
在咱们的配置文件中,一个bean标签表示一个对象,id为容器中的key值,我们从容器中取出来对应对象的唯一标识,class为对象所在的类路径。
bean下面的子标签property表示该对象需要依赖其它对象,name表示该对象依赖的属性名,ref引用当前IOC中的bean,表示要将容器中的某个bean装配在当前bean中。
那么就这么一个简单的配置的文件,如何完成强大的功能了,咱们接着看:
3.1.2.2 创建BeanFactory接口
package com.ignorance.ioc;
/**
* @author ignorance0916
* @descript beanFactory
*/
public interface BeanFactory {
/**
* @descipt 通过key获取IOC容器中的bean对象
* @param iocKey
* @return
*/
Object getBean(String iocKey);
}
3.1.2.3 创建IOC核心类
package com.ignorance.ioc;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author ignorance0916
* @descript IOC容器核心类
*/
public class ClassPathXmlApplicationContext implements BeanFactory{
//存放bean对象的map容器
private Map<String,Object> beanMap = new HashMap<>();
@Override
public Object getBean(String iocKey) {
return beanMap.get(iocKey);
}
public ClassPathXmlApplicationContext(String iocFilePath) {
SAXReader saxReader = new SAXReader();
try {
//通过dom4j解析配置文件得到一棵结点树
Document document = saxReader.read(ClassPathXmlApplicationContext.class.getClassLoader().getResourceAsStream(iocFilePath));
//得到根节点<beans></bean>
Element rootElement = document.getRootElement();
//通过根节点去查找bean标签
List<Element> beanList = rootElement.elements("bean");
/**
* 遍历所有的bean标签,以id属性作为key
* class属性值为当前的类路径,通过反射创建出来的对象作为value
* 最后将其存储在beanMap中
*/
for (Element bean : beanList){
//得到每个bean的id属性
Attribute id = bean.attribute("id");
//得到每个bean的class属性
Attribute clazzPath = bean.attribute("class");
//通过当前bean的class属性值创建出对应的对象
Object instance = Class.forName(clazzPath.getData().toString()).newInstance();
//以id属性作为key,创建出的对象作为value,存储在beanMap中
beanMap.put(id.getData().toString(),instance);
}
/**
* 一个对象可能存在属性,我们需要解析每个bean标签的子节点property标签
* 通过ref获得依赖Map中的依赖对象
* name表示当前bean中的属性值
* 通过反射拿到当前对象的属性,并且将属性初始化ref所对应的对象
*/
for (Element bean : beanList){
//得到beanMap中的当前bean所对应的实例对象
Object o1 = beanMap.get(bean.attribute("id").getData().toString());
//解析当前bean节点的property标签
List<Element> propertyList = bean.elements("property");
if (propertyList == null || propertyList.size() == 0){
continue;
}
for (Element property : propertyList){
//获取当前property的name属性
Attribute name = property.attribute("name");
//获取当前property的ref属性
Attribute ref = property.attribute("ref");
//通过ref拿到存储在beanMap中实例对象
Object o2 = beanMap.get(ref.getData());
Class<?> clazz = o1.getClass();
//将外层bean的对应属性设置为ref对应的实例对象,完成属性的装配
Field field = clazz.getDeclaredField(property.attribute("name").getData().toString());
field.setAccessible(true);
field.set(o1,o2);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
在IOC核心类中,咱们通过beanMap来存储咱们的键值对对象,以bean标签的id作为key,实例化对象作为value,解析时将其存放在beanMap中。对于xml配置文件,通过dom4j以及反射技术完成对applicationContext.xml的解析与bean对象的装配组合,提供getBean方法通过key获取咱们对应的bean对象。具体的效果如下:
3.1.2.4 配置监听器
在web.xml配置全局初始化参数,维护IOC配置文件:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>applicationContext.xml</param-value>
</context-param>
监听器在服务器启动时创建并调用IOC核心类初始化咱们底层的IOC容器。
package com.ignorance.ioc;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* @author ignorance0916
* @descript 监听器,服务器启动时完成IOC容器的初始化工作
*/
@WebListener
public class IOCServletContextListener implements ServletContextListener {
static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation" ;
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("正在初始化IOC容器......................");
//获取上下文对象
ServletContext servletContext = sce.getServletContext();
//获取IOC配置文件的地址
String iocFilePath = servletContext.getInitParameter(CONTEXT_CONFIG_LOCATION);
//得到IOC容器
BeanFactory beanFactory = new ClassPathXmlApplicationContext(iocFilePath);
//将beanFactory对象保存到application作用域,目的是让所有servlet都能共享
servletContext.setAttribute("applicationContext",beanFactory);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
3.1.2.5 提供IOC对外暴露接口
package com.ignorance.utils;
import com.ignorance.ioc.BeanFactory;
import javax.servlet.http.HttpServletRequest;
/**
* @author ignorance0916
* @descript IOC工具类
*/
public class ApplicationUtils {
public static BeanFactory getBeanFactory(HttpServletRequest request){
return (BeanFactory) request.getServletContext().getAttribute("applicationContext");
}
public static Object getBean(HttpServletRequest request , String id){
return getBeanFactory(request).getBean(id);
}
}
3.2DispatcherServlet
3.2.1 概念
DispatcherServlet主要为咱们项目的中央控制器,主要用于绑定客户端,我们所有的请求都会经过DispatcherServlet进行拦截处理,我们可以通过配置参数的方式让其路由到不同的分控制器进行处理。
3.2.2 实现
3.2.2.1 配置映射
表示客户端发送的所有带.do后缀的请求都能被咱们的DispatcherServlet拦截处理:
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>com.ignorance.mvc.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
3.2.2.2 DispatcherServlet核心类
package com.ignorance.mvc;
import com.ignorance.ioc.BeanFactory;
import com.ignorance.ioc.ClassPathXmlApplicationContext;
import com.ignorance.servlet.ViewBaseServlet;
import com.ignorance.utils.ApplicationUtils;
import org.thymeleaf.util.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* @author ignorance0916
* @descript 中央处理器
*/
public class DispatcherServlet extends ViewBaseServlet {
public static final String SESSION = "session";
public static final String REQUEST = "req";
public static final String REDIRECT = "redirect";
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//得到客户端请求url
String requestURL = req.getRequestURL().toString();
/**
* 拼接字符串,去除url后面的后缀与前面的前缀/
*/
String iocKey = requestURL.substring(requestURL.lastIndexOf("/") + 1, requestURL.indexOf(".do"));
//通过key获取对应IOC中的handler分控制器
Object handler = ApplicationUtils.getBean(req,iocKey);
//通过客户端operate参数来决定调用handler中的哪个方法
String operate = req.getParameter("operate");
if (StringUtils.isEmpty(operate)){
operate = "index";
}
//通过反射得到handler分控制器中的所有方法
Method[] methods = handler.getClass().getDeclaredMethods();
for (Method method : methods){
//在handler中获取operate参数对应的方法
if (!operate.equals(method.getName())){
continue;
}
//暴力反射
method.setAccessible(true);
//调用方法前需要知道参数信息
Parameter[] parameters = method.getParameters();
//实参数组,因为调用方法需要指定实参列表
Object[] parametersValues = new Object[parameters.length];
for (int i = 0;i < parameters.length;i++){
//得到参数名
String parameterName = parameters[i].getName();
if (SESSION.equals(parameterName)){
HttpSession session = req.getSession();
parametersValues[i] = session;
}else if(REQUEST.equals(parameterName)){
parametersValues[i] = req;
}else {
String paramValue = req.getParameter(parameterName);
Object paramValueObj = null ;
if(paramValue!=null) {
String parameterType = parameters[i].getType().getName();
switch (parameterType) {
case "java.lang.Integer":
paramValueObj = Integer.parseInt(paramValue);
break;
case "java.lang.String":
paramValueObj = paramValue;
break;
case "java.lang.Double":
paramValueObj = Double.parseDouble(paramValue);
break;
default:
throw new RuntimeException("不能处理的类型:" + parameterType);
}
}
parametersValues[i] = paramValueObj ;
}
}
try {
Object returnObj = method.invoke(handler, parametersValues);
if (returnObj != null){
String returnVal = returnObj.toString();
if (returnVal.startsWith(REDIRECT)){
String redirectUrl = returnVal.substring(returnVal.indexOf(":") + 1);
resp.sendRedirect(redirectUrl);
}else {
super.processTemplate(returnVal,req,resp);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("【调用分控制器的" + operate +"方法出错】");
}
}
}
}
四、原始架构改造
4.1架构图
针对原项目我们遇到的一些缺陷,使用全新的思想以及架构,我们将IOC容器以及DispatcherServlet处理器引入,项目具体工作流程如下图:
4.2案例改造
4.2.1 web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>view-prefix</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>com.ignorance.mvc.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
4.2.2 beanFactory接口
package com.ignorance.ioc;
/**
* @author ignorance0916
* @descript beanFactory
*/
public interface BeanFactory {
/**
* @descipt 通过key获取IOC容器中的bean对象
* @param iocKey
* @return
*/
Object getBean(String iocKey);
}
4.2.3 IOC核心类
package com.ignorance.ioc;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author ignorance0916
* @descript IOC容器核心类
*/
public class ClassPathXmlApplicationContext implements BeanFactory{
//存放bean对象的map容器
private Map<String,Object> beanMap = new HashMap<>();
@Override
public Object getBean(String iocKey) {
return beanMap.get(iocKey);
}
public ClassPathXmlApplicationContext(String iocFilePath) {
SAXReader saxReader = new SAXReader();
try {
//通过dom4j解析配置文件得到一棵结点树
Document document = saxReader.read(ClassPathXmlApplicationContext.class.getClassLoader().getResourceAsStream(iocFilePath));
//得到根节点<beans></bean>
Element rootElement = document.getRootElement();
//通过根节点去查找bean标签
List<Element> beanList = rootElement.elements("bean");
/**
* 遍历所有的bean标签,以id属性作为key
* class属性值为当前的类路径,通过反射创建出来的对象作为value
* 最后将其存储在beanMap中
*/
for (Element bean : beanList){
//得到每个bean的id属性
Attribute id = bean.attribute("id");
//得到每个bean的class属性
Attribute clazzPath = bean.attribute("class");
//通过当前bean的class属性值创建出对应的对象
Object instance = Class.forName(clazzPath.getData().toString()).newInstance();
//以id属性作为key,创建出的对象作为value,存储在beanMap中
beanMap.put(id.getData().toString(),instance);
}
/**
* 一个对象可能存在属性,我们需要解析每个bean标签的子节点property标签
* 通过ref获得依赖Map中的依赖对象
* name表示当前bean中的属性值
* 通过反射拿到当前对象的属性,并且将属性初始化ref所对应的对象
*/
for (Element bean : beanList){
//得到beanMap中的当前bean所对应的实例对象
Object o1 = beanMap.get(bean.attribute("id").getData().toString());
//解析当前bean节点的property标签
List<Element> propertyList = bean.elements("property");
if (propertyList == null || propertyList.size() == 0){
continue;
}
for (Element property : propertyList){
//获取当前property的name属性
Attribute name = property.attribute("name");
//获取当前property的ref属性
Attribute ref = property.attribute("ref");
//通过ref拿到存储在beanMap中实例对象
Object o2 = beanMap.get(ref.getData());
Class<?> clazz = o1.getClass();
//将外层bean的对应属性设置为ref对应的实例对象,完成属性的装配
Field field = clazz.getDeclaredField(property.attribute("name").getData().toString());
field.setAccessible(true);
field.set(o1,o2);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.2.4 IOC监听器
package com.ignorance.ioc;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* @author ignorance0916
* @descript 监听器,服务器启动时完成IOC容器的初始化工作
*/
@WebListener
public class IOCServletContextListener implements ServletContextListener {
static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation" ;
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("正在初始化IOC容器......................");
//获取上下文对象
ServletContext servletContext = sce.getServletContext();
//获取IOC配置文件的地址
String iocFilePath = servletContext.getInitParameter(CONTEXT_CONFIG_LOCATION);
//得到IOC容器
BeanFactory beanFactory = new ClassPathXmlApplicationContext(iocFilePath);
//将beanFactory对象保存到application作用域,目的是让所有servlet都能共享
servletContext.setAttribute("applicationContext",beanFactory);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
4.2.5 IOC对外暴露接口
package com.ignorance.utils;
import com.ignorance.ioc.BeanFactory;
import javax.servlet.http.HttpServletRequest;
/**
* @author ignorance0916
* @descript IOC工具类
*/
public class ApplicationUtils {
public static BeanFactory getBeanFactory(HttpServletRequest request){
return (BeanFactory) request.getServletContext().getAttribute("applicationContext");
}
public static Object getBean(HttpServletRequest request , String id){
return getBeanFactory(request).getBean(id);
}
}
4.2.6 DispatcherServlet中央处理器
package com.ignorance.mvc;
import com.ignorance.ioc.BeanFactory;
import com.ignorance.ioc.ClassPathXmlApplicationContext;
import com.ignorance.servlet.ViewBaseServlet;
import com.ignorance.utils.ApplicationUtils;
import org.thymeleaf.util.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* @author ignorance0916
* @descript 中央处理器
*/
public class DispatcherServlet extends ViewBaseServlet {
public static final String SESSION = "session";
public static final String REQUEST = "req";
public static final String REDIRECT = "redirect";
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//得到客户端请求url
String requestURL = req.getRequestURL().toString();
/**
* 拼接字符串,去除url后面的后缀与前面的前缀/
*/
String iocKey = requestURL.substring(requestURL.lastIndexOf("/") + 1, requestURL.indexOf(".do"));
//通过key获取对应IOC中的handler分控制器
Object handler = ApplicationUtils.getBean(req,iocKey);
//通过客户端operate参数来决定调用handler中的哪个方法
String operate = req.getParameter("operate");
if (StringUtils.isEmpty(operate)){
operate = "index";
}
//通过反射得到handler分控制器中的所有方法
Method[] methods = handler.getClass().getDeclaredMethods();
for (Method method : methods){
//在handler中获取operate参数对应的方法
if (!operate.equals(method.getName())){
continue;
}
//暴力反射
method.setAccessible(true);
//调用方法前需要知道参数信息
Parameter[] parameters = method.getParameters();
//实参数组,因为调用方法需要指定实参列表
Object[] parametersValues = new Object[parameters.length];
for (int i = 0;i < parameters.length;i++){
//得到参数名
String parameterName = parameters[i].getName();
if (SESSION.equals(parameterName)){
HttpSession session = req.getSession();
parametersValues[i] = session;
}else if(REQUEST.equals(parameterName)){
parametersValues[i] = req;
}else {
String paramValue = req.getParameter(parameterName);
Object paramValueObj = null ;
if(paramValue!=null) {
String parameterType = parameters[i].getType().getName();
switch (parameterType) {
case "java.lang.Integer":
paramValueObj = Integer.parseInt(paramValue);
break;
case "java.lang.String":
paramValueObj = paramValue;
break;
case "java.lang.Double":
paramValueObj = Double.parseDouble(paramValue);
break;
default:
throw new RuntimeException("不能处理的类型:" + parameterType);
}
}
parametersValues[i] = paramValueObj ;
}
}
try {
Object returnObj = method.invoke(handler, parametersValues);
if (returnObj != null){
String returnVal = returnObj.toString();
if (returnVal.startsWith(REDIRECT)){
String redirectUrl = returnVal.substring(returnVal.indexOf(":") + 1);
resp.sendRedirect(redirectUrl);
}else {
super.processTemplate(returnVal,req,resp);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("【调用分控制器的" + operate +"方法出错】");
}
}
}
}
4.2.7 后台数据工具类
package com.ignorance.utils;
import com.ignorance.bean.Student;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author ignorance0916
* @descript 学生数据工具类,主要提高学生的增删改查方法
*/
public class StudentUtils {
private static final Map<Integer,Student> STUDENT_MAP;
static {
STUDENT_MAP = new HashMap<>();
Student s1 = new Student(1001,"周芷若",18,"女","计算机科学与技术",120F);
Student s2 = new Student(1002,"张无忌",22,"男","软件工程",148f);
Student s3 = new Student(1003,"黄蓉",21,"女","经济管理",141f);
Student s4 = new Student(1004,"夏诗涵",19,"女","计算机科学与技术",137F);
Student s5 = new Student(1005,"欧阳锋",26,"男","土木工程",87F);
Student s6 = new Student(1006,"郭靖",23,"男","数学",99F);
STUDENT_MAP.put(s1.getNo(),s1);
STUDENT_MAP.put(s2.getNo(),s2);
STUDENT_MAP.put(s3.getNo(),s3);
STUDENT_MAP.put(s4.getNo(),s4);
STUDENT_MAP.put(s5.getNo(),s5);
STUDENT_MAP.put(s6.getNo(),s6);
}
public static List<Student> getData(){
return STUDENT_MAP.values().stream().collect(Collectors.toList());
}
public static void remove(Integer no){
STUDENT_MAP.remove(no);
}
public static Student getStudentByNo(Integer no){
return STUDENT_MAP.get(no);
}
public static void modify(Student student){
boolean flag = false;
if (!STUDENT_MAP.containsKey(student.getNo())){
return;
}
STUDENT_MAP.put(student.getNo(),student);
}
public static List<Student> findByName(Student student){
return STUDENT_MAP.values().stream().filter(e -> e.getName().equals(student.getName())).collect(Collectors.toList());
}
}
4.2.8 分控制器handler
package com.ignorance.controller;
import com.ignorance.bean.Student;
import com.ignorance.service.StudentService;
import com.ignorance.servlet.ViewBaseServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
/**
* @author ignorance0916
* @descript 学生处理器
*/
public class StudentController {
private StudentService studentService;
public String index(HttpServletRequest req){
List<Student> list = studentService.findAll();
req.setAttribute("list",list);
return "index";
}
public String del(Integer no){
studentService.del(no);
return "redirect:student.do?operate=index";
}
}
4.2.9 客户端index.html页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
<meta charset="UTF-8">
<title>学生管理系统</title>
<style>
table{
width: 600px;
text-align: center;
margin:200px auto;
background: linear-gradient(dodgerblue,white,lightskyblue);
}
</style>
</head>
<body>
<table border="1px solid" cellpadding="0" cellspacing="0">
<tr>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>专业</th>
<th>分数</th>
<th>操作</th>
</tr>
<tr th:if="${not #lists.isEmpty(list)}"th:each="item : ${list}">
<td th:text="${item.no}"></td>
<td th:text="${item.name}"></td>
<td th:text="${item.age}"></td>
<td th:text="${item.gender}"></td>
<td th:text="${item.major}"></td>
<td th:text="${item.score}"></td>
<td><a th:href="@{/student.do(no=${item.no},operate='del')}" style="color: red;text-decoration: none;text-align: center">删除</a></td>
</tr>
<tr th:unless="${not #lists.isEmpty(list)}">
<td colspan="7">暂无学生信息</td>
</tr>
</table>
</body>
</html>
4.2.10 运行结果
总结
以上是本篇文章讲解的主要内容,在本次讲解中,咱们主要通过手写一个IOC容器以及DispatcherServlet完成对咱们web原始架构的改造,解决了我们原始开发遇到的最棘手的两个问题,所以后来我们会学习效率更高的的框架来提升咱们的开发效率,减轻程序员的开发压力。
以上的内容是一个很简单的底层操作,Spring的底层解析肯定会比咱们写得复杂全面的多,之所以写这个例子,是想通过本次讲解告诉同学们反射的魅力所在以及底层设计思想的重要性。虽然没有这些东西,我们的开发也能正常完成,但是你仔细研究的话,会发现底层的原理远远要比会用框架要更有价值或者技术含量。
另外,很多同学一直在困惑反射是怎么用得,这其实就是一个常用的反射场景,它的重要性就体现在动态二字,这种思想不用多说,我相信大家能够用心体会到。而这些也只是庞大的Spring生态体系中的冰山一角,咱们是技术人,技术在不断更新迭代,我们学习的路一直都没有终点,一直都还长。所以呢,道友们,加油吧!技术一定会帮助努力的人得到自己想要的一切!