文章持续更新中…
目前字数: 15590
首先这是一个基础的Maven项目,且项目只需要依赖于一个Servlet-api即可。以下是最基础的pom文件,也是整个项目从头到尾所需要的依赖。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>implementSpringartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
dependency>
dependencies>
project>
在resource文件夹下新建一个application.properties 文件,里面暂时写一个k-v ,对应的value值是我们要扫描的包的路径。
scannerPackage = org.example.wzl
新建包为 mvc_farmeworke,且在下面暂时有一个包为 dispatcher,里面存放我们自己写的DispatcherServlet,
在这里我的DispatcherServlet暂且命名为ZLDispatcherServlet。
在完成上述工作后,我们配置对应的web.xml配置文件,配置DispatcherServlet为我们自己的DispatcherServlet。
<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">
<display-name>Wzl Web Applicationdisplay-name>
<servlet>
<servlet-name>wzlMvcservlet-name>
<servlet-class>org.example.wzl.mvc_framework.dispatcher.ZLDispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:application.propertiesparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>wzlMvcservlet-name>
<url-pattern>/*url-pattern>
servlet-mapping>
web-app>
在完成以上基础项目搭建后,我们开始进行代码书写。
其实本质上,DispatcherServlet也是一个Servlet,他帮我们做了很多处理,做了封装。那么我们也是需要继承自HttpServlet,于是我们这个类的最基本结构就有了
public class ZLDispatcherServlet extends HttpServlet {
}
我们要做的事情如果是使用Servlet,那么我们直接重写doPost/Get即可,或者重写service即可。但是我们的目的是写一个我们自己的DispatcherServlet,我们就不能这样干。上面的执行流程也有说。存在初始化,存在方法调用doGet/Post,这些方法在HttpServlet和他的继承类GenericServlet做了实现,其中doGet/Post在HttpServlet中,init方法在GenericServlet中,于是就有了下面的代码
public class ZLDispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
public void init(ServletConfig config) throws ServletException {
}
}
接下来我们就按照我们的步骤进行分析,我们主要的主打方法,在init()中。我们不论怎么样,一定是要先进行初始化的,首先我们会调用init方法,去加载配置文件,这一点是必不可少的。先读取web.xml中的配置文件,于是我们就可以写第一个方法。我们将方法定义为如下格式: doLoadConfig(config.getInitParameter(APPLICATION_LOCATION));
因为我们需要通过servlet提供的ServletConfig 在web.xml中取值,我们通过他就可以取值到我们init-param中的value。我们需要先获取到配置文件的位置。其中,APPLICATION_LOCATION为常量定义。
public class ZLDispatcherServlet extends HttpServlet {
// 加载Pro文件
private final Properties properties = new Properties();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
public void init(ServletConfig config) throws ServletException {
// 1. 加载配置文件
String APPLICATION_LOCATION = "contextConfigLocation";
doLoadConfig(config.getInitParameter(APPLICATION_LOCATION));
}
/**
* 加载配置文件
*/
private void doLoadConfig(String initParameter) {
InputStream is = null;
try {
is = this.getClass().getClassLoader().getResourceAsStream(initParameter);
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != is) {
is.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private void doLoadConfig(String initParameter) 方法介绍。
我们需要通过this.getClass().getClassLoader().getResourceAsStream()获取到一个配置文件流对象,然后我们会通过Java提供的Properties对象,来把这个流对象转换为Properties对象即可。Properties对象的实例是一个成员变量。
在完成配置文件的加载后,第二步就是容器初始化,对于容器初始化,其实就较为简单。我们需要创建一个IOC容器,也就是Map
public class ZLDispatcherServlet extends HttpServlet {
// 加载Pro文件
private final Properties properties = new Properties();
// 2. 容器初始化
private final Map<String, Object> ioc = new HashMap<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
public void init(ServletConfig config) throws ServletException {
// 1. 加载配置文件
String APPLICATION_LOCATION = "contextConfigLocation";
doLoadConfig(config.getInitParameter(APPLICATION_LOCATION));
}
/**
* 加载配置文件
*/
private void doLoadConfig(String initParameter) {
InputStream is = null;
try {
is = this.getClass().getClassLoader().getResourceAsStream(initParameter);
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != is) {
is.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
第三步,扫描包下的类。相关的类。这个就是把我们配置文件application.properties中配置的scanPackage中的value值进行包扫描,然后扫描到的包下的类及其子类进行暂存,暂存到一个集合中。
public class ZLDispatcherServlet extends HttpServlet {
// 加载Pro文件
private final Properties properties = new Properties();
// 2. 容器初始化
private final Map<String, Object> ioc = new HashMap<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
public void init(ServletConfig config) throws ServletException {
// 1. 加载配置文件
String APPLICATION_LOCATION = "contextConfigLocation";
doLoadConfig(config.getInitParameter(APPLICATION_LOCATION));
// 3. 扫描相关的类
doScanner(properties.getProperty("scannerPackage"));
}
/**
* 加载配置文件
*/
private void doLoadConfig(String initParameter) {
InputStream is = null;
try {
is = this.getClass().getClassLoader().getResourceAsStream(initParameter);
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != is) {
is.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
doScanner(properties.getProperty(“scannerPackage”));
方法介绍,用来进行包扫描,传入一个对象为我们配置文件中k对应的value,也就是 xxx.xx.xx包路径
实际的方法为
private void doScanner(String scannerPackage) {}
需要一个String对象,具体的需要内容为需要的scanPacakage对应的value值。获取到对应的要扫描的包路径。
/**
* 扫描相关的类
*/
private void doScanner(String scannerPackage) {
// 做替换,找到包下的所有的内容。
URL url = this.getClass().getClassLoader().getResource(scannerPackage.replaceAll("\\.", "/"));
// 拿到文件夹的路径
assert url != null;
File classPath = new File(url.getFile());
// 对这个文件夹进行迭代,看看里面都有那些文件
for (File file : Objects.requireNonNull(classPath.listFiles())) {
// 包里可能存在另外的包,所以需要迭代
if (file.isDirectory()) {
// 进行递归。用现有的包名称 + 子包
doScanner(scannerPackage + "/" + file.getName());
} else {
// 包下可能存在很多文件类型如,.xml , .pro .txt等,所以我们只需要扫描class文件,如果不是,则回溯, 反之继续向下
if (!file.getName().endsWith(".class")) {continue;}
// 通过拼接获取到一个全包类名 如 org.example.wzl.Main
String className = (scannerPackage + "." + file.getName().replaceAll(".class", ""));
// 暂存到容器中
classNames.add(className);
// fullClassName 进行反射
}
}
}
逐行解析一下这个方法里面的代码
URL url = this.getClass().getClassLoader().getResource(scannerPackage.replaceAll("\\.", "/"));
第一行代码,获取到一个url对象,通过this.getClass().getClassLoader().getResource()获取到classpath下的路径,然后通过我们获取到的配置文件内的 xxx.xxx.xx 然后做拼接,并且把 符号 . 替换为 /,从而拼接成一个完整的url路径。
assert url != null;
File classPath = new File(url.getFile());
// 对这个文件夹进行迭代,看看里面都有那些文件
for (File file : Objects.requireNonNull(classPath.listFiles())) {
// 包里可能存在另外的包,所以需要迭代
if (file.isDirectory()) {
// 进行递归。用现有的包名称 + 子包
doScanner(scannerPackage + "/" + file.getName());
} else {
// 包下可能存在很多文件类型如,.xml , .pro .txt等,所以我们只需要扫描class文件,如果不是,则回溯, 反之继续向下
if (!file.getName().endsWith(".class")) {continue;}
// 通过拼接获取到一个全包类名 如 org.example.wzl.Main
String className = (scannerPackage + "." + file.getName().replaceAll(".class", ""));
// 暂存到容器中
classNames.add(className);
// fullClassName 进行反射
}
}
然后首先使用断言判断一下url不为空,然后执行下面的File实例的创建,使用url.getFile()这个方法,获取到完整的File路径,然后创建File实例。
在拥有File实例后,通过迭代,来看看里面都有那些文件,首先需要判断你这个迭代到的File对象是不是一个文件夹,因为包里面还有包,包实际上就是一个文件夹。如果是文件夹的话,我们也需要遍历文件夹内的内容,所以只需要通过递归即可,传入现有的包名 + 文件夹名称
doScanner(scannerPackage + "/" + file.getName());
那么另一种情况就是,如果这个不是一个文件夹,我们就需要判断一下他是不是其他文件,因为我们只要.class结尾的Java文件。所以我们做了以下判断。
if (!file.getName().endsWith(".class")) {continue;}
如果这个名称的结尾不等于.class,我们直接回退即可下面代码无需执行。如果他是一个Java文件的话,我们就需要把他拼接起来,拼成一个全包类名,比如 org.wzl.pojo.Main 这种格式的。于是就有了这一行代码
String className = (scannerPackage + "." + file.getName().replaceAll(".class", ""));
这个要点也就是 .replaceAll(“.class”, “”); 其实也就是把结尾的.class 替换为 空字符串。于是我们就得到了一个全包类名,我们会创建一个暂时的容器,为一个List集合。将这个全包类名存储到该集合中(暂存)。
最后得到已下完整代码
public class ZLDispatcherServlet extends HttpServlet {
// 加载Pro文件
private final Properties properties = new Properties();
// 2. 容器初始化
private final Map<String, Object> ioc = new HashMap<>();
// 存入全包类名的容器。
private final List<String > classNames = new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
public void init(ServletConfig config) throws ServletException {
// 1. 加载配置文件
String APPLICATION_LOCATION = "contextConfigLocation";
doLoadConfig(config.getInitParameter(APPLICATION_LOCATION));
// 3. 扫描相关的类
doScanner(properties.getProperty("scannerPackage"));
}
/**
* 扫描相关的类
*/
private void doScanner(String scannerPackage) {
// 做替换,找到包下的所有的内容。
URL url = this.getClass().getClassLoader().getResource(scannerPackage.replaceAll("\\.", "/"));
// 拿到文件夹的路径
assert url != null;
File classPath = new File(url.getFile());
// 对这个文件夹进行迭代,看看里面都有那些文件
for (File file : Objects.requireNonNull(classPath.listFiles())) {
// 包里可能存在另外的包,所以需要迭代
if (file.isDirectory()) {
// 进行递归。用现有的包名称 + 子包
doScanner(scannerPackage + "/" + file.getName());
} else {
// 包下可能存在很多文件类型如,.xml , .pro .txt等,所以我们只需要扫描class文件,如果不是,则回溯, 反之继续向下
if (!file.getName().endsWith(".class")) {continue;}
// 通过拼接获取到一个全包类名 如 org.example.wzl.Main
String className = (scannerPackage + "." + file.getName().replaceAll(".class", ""));
// 暂存到容器中
classNames.add(className);
// fullClassName 进行反射
}
}
}
/**
* 加载配置文件
*/
private void doLoadConfig(String initParameter) {
InputStream is = null;
try {
is = this.getClass().getClassLoader().getResourceAsStream(initParameter);
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != is) {
is.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
在扫描全包类名完成后,下一步要进行的就是初始化所有实例,并且将创建好的实例放入到IOC容器中。我们使用这个方法 doInstance(); 其实代码很简单,如下
/**
* 初始化所有类相关的实例,并且放入到IOC容器中
*/
private void doInstance() {
// 如果classNames容器内的内容为空,则直接返回即可
if (classNames.isEmpty()) {return;}
try {
for (String className : classNames) {
Class<?> aClass = Class.forName(className);
// 通过反射创建新实例
Object instance = aClass.newInstance();
// 创建bean key 规则为 首字母小写,其他驼峰 通过getSimpleName获取到纯粹的类名。默认大写
String iocBeanKey = toLowerFirstCase(aClass.getSimpleName());
// IOC容器添加
ioc.put(iocBeanKey, instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
依然是逐行分析一下,首先做了一个判断,如果我们上面刚刚暂存全包类名的路径的一个集合为空,则说明一个对象都没有扫描到,则IOC的实例创建没有必要,直接return即可。
// 如果classNames容器内的内容为空,则直接返回即可
if (classNames.isEmpty()) {return;}
如果该集合内存在数据,则说明实实在在的扫描到了已存在的对象,且以全包类名的形式进行存在,那么既然有全包类名,即可通过反射来进行对象的实例化。代码如下
try {
for (String className : classNames) {
Class<?> aClass = Class.forName(className);
// 通过反射创建新实例
Object instance = aClass.newInstance();
// 创建bean key 规则为 首字母小写,其他驼峰 通过getSimpleName获取到纯粹的类名。默认大写
String iocBeanKey = toLowerFirstCase(aClass.getSimpleName());
// IOC容器添加
ioc.put(iocBeanKey, instance);
} catch (Exception e) {
e.printStackTrace();
}
首先遍历我们刚刚的暂存容器,里面存放我们扫描到的全包类名,然后我们首先通过Class.forName要求Jvm去找到并且返回对应的类,然后我们可以通过Jvm提供到的Class> aClass对象,来对该对象进行一个实例化,也就是以下一行代码
// 通过反射创建新实例
Object instance = aClass.newInstance();
听过反射创建实例完成后,那我们就需要对其bean名称命名规则进行一定的定义,首先大家都知道的是,在Spring框架中,Ioc的bean命名规则默认为类的首字母小写且后续驼峰。如 PojoService 在Ioc容器中默认的名字为pojoService,所以我们需要对我们的一个类名称进行一个解析,我们单独的拉出去一个专门进行该功能实现的方法,芳芳名称为 toLowerFirstCase(String className);需要传入一个String类型的值,返回一个首字母小写且其他为驼峰命名法的单词。以此来实现Ioc容器中Bean的命名规则。
/**
* 返回一个首字母小写且驼峰命名法的单词
*/
private String toLowerFirstCase(String simpleName) {
char[] ascii = simpleName.toCharArray();
// 防止有些傻逼类首字母小写,做ascii判断
if (ascii[0] > 96 && ascii[0] < 123) {
// 在这个区间内说明就是小写了
return String.valueOf(ascii);
}
// 使其大写变小写 ascii码
ascii[0] += 32;
return String.valueOf(ascii);
}
上述代码做了实现,采用ASCII码的形式进行了一个较为舒适的转换。且同时判断了如果一开始类就是小写的突发情况。在做了上述工作后,我们就可以得到一个完美符合bean命名规则的一个方法。
在这一行代码有进行书写。
// 创建bean key 规则为 首字母小写,其他驼峰 通过getSimpleName获取到纯粹的类名。默认大写
String iocBeanKey = toLowerFirstCase(aClass.getSimpleName());
我们获取到的bean命名规则名称为 iocBeanKey,实例有了,名称有了,接下来就是向Ioc容器中添加
// IOC容器添加
ioc.put(iocBeanKey, instance);
在写完这一步骤后,我们得到了以下的代码
public class ZLDispatcherServlet extends HttpServlet {
// 加载Pro文件
private final Properties properties = new Properties();
// 2. 容器初始化
private final Map<String, Object> ioc = new HashMap<>();
// 存入全包类名的容器。
private final List<String > classNames = new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
public void init(ServletConfig config) throws ServletException {
// 1. 加载配置文件
String APPLICATION_LOCATION = "contextConfigLocation";
doLoadConfig(config.getInitParameter(APPLICATION_LOCATION));
// 3. 扫描相关的类
doScanner(properties.getProperty("scannerPackage"));
}
/**
* 初始化所有类相关的实例,并且放入到IOC容器中
*/
private void doInstance() {
// 如果classNames容器内的内容为空,则直接返回即可
if (classNames.isEmpty()) {return;}
try {
for (String className : classNames) {
Class<?> aClass = Class.forName(className);
// 通过反射创建新实例
Object instance = aClass.newInstance();
// 创建bean key 规则为 首字母小写,其他驼峰 通过getSimpleName获取到纯粹的类名。默认大写
String iocBeanKey = toLowerFirstCase(aClass.getSimpleName());
// IOC容器添加
ioc.put(iocBeanKey, instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 返回一个首字母小写且驼峰命名法的单词
*/
private String toLowerFirstCase(String simpleName) {
char[] ascii = simpleName.toCharArray();
// 防止有些傻逼类首字母小写,做ascii判断
if (ascii[0] > 96 && ascii[0] < 123) {
// 在这个区间内说明就是小写了
return String.valueOf(ascii);
}
// 使其大写变小写 ascii码
ascii[0] += 32;
return String.valueOf(ascii);
}
/**
* 扫描相关的类
*/
private void doScanner(String scannerPackage) {
// 做替换,找到包下的所有的内容。
URL url = this.getClass().getClassLoader().getResource(scannerPackage.replaceAll("\\.", "/"));
// 拿到文件夹的路径
assert url != null;
File classPath = new File(url.getFile());
// 对这个文件夹进行迭代,看看里面都有那些文件
for (File file : Objects.requireNonNull(classPath.listFiles())) {
// 包里可能存在另外的包,所以需要迭代
if (file.isDirectory()) {
// 进行递归。用现有的包名称 + 子包
doScanner(scannerPackage + "/" + file.getName());
} else {
// 包下可能存在很多文件类型如,.xml , .pro .txt等,所以我们只需要扫描class文件,如果不是,则回溯, 反之继续向下
if (!file.getName().endsWith(".class")) {continue;}
// 通过拼接获取到一个全包类名 如 org.example.wzl.Main
String className = (scannerPackage + "." + file.getName().replaceAll(".class", ""));
// 暂存到容器中
classNames.add(className);
// fullClassName 进行反射
}
}
}
/**
* 加载配置文件
*/
private void doLoadConfig(String initParameter) {
InputStream is = null;
try {
is = this.getClass().getClassLoader().getResourceAsStream(initParameter);
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != is) {
is.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}