感觉改造的历程还是非常有意思的
第一版
以模块为单位放到一个servlet中去,然后在servlet中创建对应的方法,并以method来标注调用的是哪个方法;
@WebServlet(urlPatterns="/linkMan")
public class LinkManServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
//1.接收参数:action--要执行的方法名称
String action = request.getParameter("action");
//2.反射调用名称为action的方法
Class clazz = this.getClass();
Method method = clazz.getMethod(action, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
public void queryAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//....
}
public void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//....
}
}
缺陷:每个模块都会大量的来重用doGet/doPost方法,代码冗余。
解决方式:将多个模块之间的共同方法:doPost/doGet方法抽取到父类中去
第二版
解决模块化Servlet的缺点,抽取BaseServlet。每个模块的类,不要再继承HttpServlet
,而是继承BaseServlet
public class BaseServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
//1.接收参数:action--要执行的方法名称
String action = request.getParameter("action");
//2.反射调用名称为action的方法
Class clazz = this.getClass();
Method method = clazz.getMethod(action, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
然后每个模块创建对应的servlet来继承这个BaseServlet类
@WebServlet(urlPatterns="/linkMan")
public class LinkManServlet extends BaseServlet {
public void queryAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//....
}
public void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//....
}
}
上面的问题也很显然,多个模块之间的servlet类之间仍然存在着耦合性。所以还需要来进行改进。
BaseServlet的问题
- 核心控制器:BaseServlet。
- 与业务功能无关,是请求请求,调用业务功能方法的
- 业务控制器:UserServlet和LinkManServlet...
- 处理业务功能逻辑的
- 问题是:这种方式不方便,并且耦合性比较高
- 无论什么功能,客户端都必须要传参一个action,指定方法名称;否则核心控制器无法找到要执行的方法。
- 业务控制器必须要继承BaseServlet,因为解析请求路径、调用对应方法的代码在BaseServlet方法中。
- 理想的方式是:
- 业务控制器就是普通的Java类,不需要继承任何父类,降低耦合性
- 客户端不需要通过传参,核心控制器就能够区分请求的是哪个方法
解决方案
- 给业务控制器里每个方法,都使用注解配置一个字符串值:虚拟访问路径(映射路径)
- 客户端直接发请求,请求信息中有这个虚拟访问路径(映射路径),不需要传递任何参数
- 在核心控制器里:拦截一切
.do
结尾的请求- 解析请求路径,得到requestPath
- 根据requestPath,找到对应的方法Method
- 调用执行这个方法
看一下对应的版本
V1版本
定义一个注解类Controller(暂时不用)和RequestMapping
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
String value();
}
@Target(value={ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value();
}
对应的servlet的代码:
@WebServlet(name = "DispatcherServlet", urlPatterns = "*.do")
public class DispatcherServlet extends HttpServlet {
/**
*
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String contextPath = request.getContextPath();
int length = contextPath.length();
int var2 = requestURI.lastIndexOf(".do");
String methodPath = requestURI.substring(length, var2);
// 获取得到对应的路径之后,然后来查找整个项目中的路径即可
// System.out.println("解析出来的路径是:"+methodPath);
// 在整个项目中来进行查找拼接对应的路径,获取得到所有的class文件,这些文件中保存了需要调用的方法和对应的路径
List<Class<?>> classsFromPackage = ClassScannerUtils.getClasssFromPackage("com.guang.controller");
for (Class<?> clazz : classsFromPackage) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
// 如果有注解,获取得到注解上的值(这里保存的就是对应的路径,所以这里需要来对这里的路径来做一个处理方式)
boolean annotationPresent = method.isAnnotationPresent(RequestMapping.class);
if (annotationPresent){
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
// 这里应该来做一个缓存思想
String value = annotation.value();
if (Objects.equals(methodPath,value)){
try {
// 创建类的对象进去
Object newInstance = clazz.newInstance();
method.invoke(newInstance,request,response);
return; // 找到了就将方法来进行关闭
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
弊端:
1、但是这样子来进行操作存在着一个弊端,就是每一次来进行查找的时候,都会扫描全部,所以可能会影响效率。所以应该做一个缓存思想来进行操作
2、不应该将路径写在代码中来,而是应该写在配置文件中来,在修改完成配置文件之后,直接从配置文件中加载即可。
3、servlet默认的是从第一次访问的时候来进行加载,所以我们可以修改在启动的时候来进行加载。
V2版本
public class DispatcherServlet extends HttpServlet {
/**
* key为path路径
* value是保存的对象
*/
private static Map<String,MethodBean> map = new ConcurrentHashMap<>();
/**
* 不应该来继承这个,而是应该继承另外一个Init重载的函数
* @throws ServletException
*/
@Override
public void init() throws ServletException {
super.init();
try {
// 这个配置不应该在这里来进行配置
List<Class<?>> classsFromPackage = ClassScannerUtils.getClasssFromPackage("com.guang.controller");
for (Class<?> clazz : classsFromPackage) {
boolean annotationPresent1 = clazz.isAnnotationPresent(Controller.class);
if (annotationPresent1){
Controller controllerAnnotation = clazz.getAnnotation(Controller.class);
String classPath = controllerAnnotation.value();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
boolean annotationPresent2 = method.isAnnotationPresent(RequestMapping.class);
if (annotationPresent2){
MethodBean methodBean = new MethodBean();
methodBean.setMethod(method);
methodBean.setObject(clazz.newInstance());
RequestMapping requestMappingAnnotation = method.getAnnotation(RequestMapping.class);
String requestMappingPath = requestMappingAnnotation.value();
// 这里要以分割线来进行分隔即可
String requestPath = classPath+requestMappingPath;
// 在获取的时候应该是:method.invoke(类的实例对象,参数);
map.put(requestPath,methodBean);
}
}
}
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 但是这样子来进行操作存在着一个弊端,就是每一次来进行查找的时候,都会扫描全部,所以可能会影响效率。所以应该做一个缓存思想来进行操作
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String contextPath = request.getContextPath();
int length = contextPath.length();
int var2 = requestURI.lastIndexOf(".do");
String methodPath = requestURI.substring(length, var2);
MethodBean methodBean = map.get(methodPath);
Method method = methodBean.getMethod();
Object object = methodBean.getObject();
try {
method.invoke(object,request,response);
} catch (Exception e) {
System.out.println("调用异常有误!!!!");
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
但是这样子来写依然无法避免上面出现的问题:
1、包名不应该直接写在代码中去,如果在项目进行运行的时候,进行修改的话非常之不方便。
关于这个比较容易解决,我们可以访问父类中的init重载方法
V3版本
public class DispatcherServlet extends HttpServlet {
/**
* key为path路径
* value是保存的对象
*/
private static Map<String,MethodBean> map = new ConcurrentHashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
try {
String scanpackage = config.getInitParameter("scanpackage");
// 包的配置应该来写到配置文件中来,这里不需要来写对应的字节码文件中来,因为需要进行修改的时候不好进行修改
List<Class<?>> classsFromPackage = ClassScannerUtils.getClasssFromPackage(scanpackage);
for (Class<?> clazz : classsFromPackage) {
boolean annotationPresent1 = clazz.isAnnotationPresent(Controller.class);
if (annotationPresent1){
Controller controllerAnnotation = clazz.getAnnotation(Controller.class);
String classPath = controllerAnnotation.value();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
boolean annotationPresent2 = method.isAnnotationPresent(RequestMapping.class);
if (annotationPresent2){
MethodBean methodBean = new MethodBean();
methodBean.setMethod(method);
methodBean.setObject(clazz.newInstance());
RequestMapping requestMappingAnnotation = method.getAnnotation(RequestMapping.class);
String requestMappingPath = requestMappingAnnotation.value();
// 这里要以分割线来进行分隔即可
String requestPath = classPath+requestMappingPath;
// 在获取的时候应该是:method.invoke(类的实例对象,参数);
map.put(requestPath,methodBean);
}
}
}
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 但是这样子来进行操作存在着一个弊端,就是每一次来进行查找的时候,都会扫描全部,所以可能会影响效率。所以应该做一个缓存思想来进行操作
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String contextPath = request.getContextPath();
int length = contextPath.length();
int var2 = requestURI.lastIndexOf(".do");
String methodPath = requestURI.substring(length, var2);
MethodBean methodBean = map.get(methodPath);
Method method = methodBean.getMethod();
Object object = methodBean.getObject();
try {
method.invoke(object,request,response);
} catch (Exception e) {
System.out.println("调用异常有误!!!!");
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
对应的包应该配置在xml中:
<servlet>
<servlet-name>dispatcherservlet</servlet-name>
<servlet-class>com.guang.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>scanpackage</param-name>
<!--这里的配置文件信息应该写在这里!表示的是扫描哪些文件路径-->
<param-value>com.guang.controller</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
如果有后续,还会来进行改进。