解决的问题

  1. servlet的数量会随业务功能的扩展而不断增加,我们有必要减少servlet的数量,交给controller处理,它负责调用service的相关方法,并将返回值放入request或response中。
  2. service目前是通过new的方式来创建的,这样导致一个应用中会创建多个对象,这样是不科学的。我们可以通过一种“依赖注入”的思想,让框架来为我们创建所需要的对象。

掌握的技能

  • 如何快速搭建开发框架
  • 如何加载并读取配置文件
  • 如何实现一个简单的IOC容器
  • 如何加载指定的类
  • 如何初始化框架

实现IOC

框架在实现IOC(Inversion of Control,控制反转)的时候,通过框架自身来示例化Bean。

Bean从哪里来,怎么获取Bean,怎么实现Bean的管理?

第一步:加载Bean

第二部:实例化Bean

第三部:根据注解,将Bean注入

开发一个类加载器

如果对类的加载机制不熟的话,可以先看看《深入理解Java虚拟机》的第七章——虚拟机类加载机制。

在Java语言里面,类型的加载和连接过程都是在运行期间完成的,这样会在类加载时稍微增加一些性能开销,但是却能为Java应用程序提供高度的灵活性,Java中天生可以动态扩展的语言特性就是依赖运行期动态加载和动态链接这个特点实现的。例如,如果编写一个使用接口的应用程序,可以等到运行时再指定其实际的实现。这种组装应用程序的方式广泛应用于Java程序之中。

目标:获取指定包名下的所有类。

分析:由className通过类加载器可以加载到内存中,将包下面的class加载并放入到Set<Class<?>>。包名可能是一个file或者一个jar包。

具体实现:

/**
* 类操作工具类
*/
public final class ClassUtil { private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class); /**
* 获取类加载器
*/
public static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
} /**
* 加载类
*/
public static Class<?> loadClass(String className, boolean isInitialized) {
Class<?> cls;
try {
cls = Class.forName(className, isInitialized, getClassLoader());
} catch (ClassNotFoundException e) {
LOGGER.error("load class failure", e);
throw new RuntimeException(e);
}
return cls;
} /**
* 加载类(默认将初始化类)
*/
public static Class<?> loadClass(String className) {
return loadClass(className, true);
} /**
* 获取指定包名下的所有类
*/
public static Set<Class<?>> getClassSet(String packageName) {
Set<Class<?>> classSet = new HashSet<Class<?>>();
try {
Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (url != null) {
String protocol = url.getProtocol();
if (protocol.equals("file")) {
String packagePath = url.getPath().replaceAll("%20", " ");
addClass(classSet, packagePath, packageName);
} else if (protocol.equals("jar")) {
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
if (jarURLConnection != null) {
JarFile jarFile = jarURLConnection.getJarFile();
if (jarFile != null) {
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String jarEntryName = jarEntry.getName();
if (jarEntryName.endsWith(".class")) {
String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
doAddClass(classSet, className);
}
}
}
}
}
}
}
} catch (Exception e) {
LOGGER.error("get class set failure", e);
throw new RuntimeException(e);
}
return classSet;
} private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
File[] files = new File(packagePath).listFiles(new FileFilter() {
public boolean accept(File file) {
return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
}
});
for (File file : files) {
String fileName = file.getName();
if (file.isFile()) {
String className = fileName.substring(0, fileName.lastIndexOf("."));
if (StringUtil.isNotEmpty(packageName)) {
className = packageName + "." + className;
}
doAddClass(classSet, className);
} else {
String subPackagePath = fileName;
if (StringUtil.isNotEmpty(packagePath)) {
subPackagePath = packagePath + "/" + subPackagePath;
}
String subPackageName = fileName;
if (StringUtil.isNotEmpty(packageName)) {
subPackageName = packageName + "." + subPackageName;
}
addClass(classSet, subPackagePath, subPackageName);
}
}
} private static void doAddClass(Set<Class<?>> classSet, String className) {
Class<?> cls = loadClass(className, false);
classSet.add(cls);
}
}

以下是两篇我对类加载器的总结:

关于类加载器

类加载器与Web容器

自定义注解

目标:在控制器类上使用Controller注解,在控制器类的方法上使用Action注解,在服务类上使用Service注解,在控制器类中可使用Inject注解将服务类依赖注入进来。

分析:JDK 5中引入了源代码中的注解(annotation)这一机制。注解使得Java源代码中不但可以包含功能性的实现代码,还可以添加元数据。注解的功能类似于代码中的注释,所不同的是注解不是提供代码功能的说明,而是实现程序功能的重要组成部分。Java注解已经在很多框架中得到了广泛的使用,用来简化程序中的配置。

Action方法注解代码如下:

/**
* Action 方法注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
/**
* 请求类型与路径
*/
String value();
}
  • @Target – 这个注解可以让你来指定你的注解应该被用在那个java元素上. 可能的目标类型是 ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER 和 TYPE. 在我们的 @ReconField 注解中他被指定到了 FIELD 级别.
  • @Retention – 它可以让你指定注解在何时生效. 可能的值有 CLASS, RUNTIME 和 SOURCE. 因为我们将会在运行时 RUNTIME 处理这个注解, 所以那就是我们需要设置的值.
  • @interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。

Java深度历险(六)——Java注解

根据注解获取相应的类

之前我们已经可以通过ClassUtil类加载指定包下所有的类,这样我们可以通过循环所有加载的类,根据class的isAnnotationPresent()来判断是否是对应注解下的类,并将获取的java.lang.class实例放进classSet。

比如获取应用包名下所有的Service类:

public static Set<Class<?>> getServiceClassSet() {
Set<Class<?>> classSet = new HashSet<Class<?>>();
for (Class<?> cls : CLASS_SET) {
if (cls.isAnnotationPresent(Service.class)) {
classSet.add(cls);
}
}
return classSet;
}

实现Bean容器

1.通过反射实例化对象。

调用class的newInstance()方法来实例化实例。

/**
* 反射工具类
*/
public final class ReflectionUtil { private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class); /**
* 创建实例
*/
public static Object newInstance(Class<?> cls) {
Object instance;
try {
instance = cls.newInstance();
} catch (Exception e) {
LOGGER.error("new instance failure", e);
throw new RuntimeException(e);
}
return instance;
} /**
* 创建实例(根据类名)
*/
public static Object newInstance(String className) {
Class<?> cls = ClassUtil.loadClass(className);
return newInstance(cls);
} /**
* 调用方法
*/
public static Object invokeMethod(Object obj, Method method, Object... args) {
Object result;
try {
method.setAccessible(true);
result = method.invoke(obj, args);
} catch (Exception e) {
LOGGER.error("invoke method failure", e);
throw new RuntimeException(e);
}
return result;
} /**
* 设置成员变量的值
*/
public static void setField(Object obj, Field field, Object value) {
try {
field.setAccessible(true);
field.set(obj, value);
} catch (Exception e) {
LOGGER.error("set field failure", e);
throw new RuntimeException(e);
}
}
}

2.将实例化的对象放进Map中,通过kay去获取对应的value(Bean对象)。

实现方法:

/**
* Bean 助手类
*/
public final class BeanHelper { private static final Map<Class<?>, Object> BEAN_MAP = new HashMap<Class<?>, Object>(); static {
Set<Class<?>> beanClassSet = ClassHelper.getBeanClassSet();
for (Class<?> beanClass : beanClassSet) {
Object obj = ReflectionUtil.newInstance(beanClass);
BEAN_MAP.put(beanClass, obj);
}
} /**
* 获取 Bean 映射
*/
public static Map<Class<?>, Object> getBeanMap() {
return BEAN_MAP;
} /**
* 获取 Bean 实例
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(Class<T> cls) {
if (!BEAN_MAP.containsKey(cls)) {
throw new RuntimeException("can not get bean by class: " + cls);
}
return (T) BEAN_MAP.get(cls);
} /**
* 设置 Bean 实例
*/
public static void setBean(Class<?> cls, Object obj) {
BEAN_MAP.put(cls, obj);
}
}

实现依赖注入功能

至此,我们基本已经完成了所有的工具类,现在将开发IocHelper来实现依赖注入。

只需要在IocHelper的静态代码块中实现相关逻辑,就能完成IOC容器的初始化工作。

当IocHelper这个类被加载的时候,就会加载它的静态代码块。后面统一在一个地方加载这个IocHelper。

代码如下:

/**
* 依赖注入助手类
*/
public final class IocHelper { static {
//获取所有的Bean类与Bean实例之间的映射关系
Map<Class<?>, Object> beanMap = BeanHelper.getBeanMap();
if (CollectionUtil.isNotEmpty(beanMap)) {
//遍历Bean Map
for (Map.Entry<Class<?>, Object> beanEntry : beanMap.entrySet()) {
//从BeanMap中获取Bean类与Bean实例
Class<?> beanClass = beanEntry.getKey();
Object beanInstance = beanEntry.getValue();
//获取Bean类定义的所有成员变量(简称Bean Field)
Field[] beanFields = beanClass.getDeclaredFields();
if (ArrayUtil.isNotEmpty(beanFields)) {
//遍历Bean Field
for (Field beanField : beanFields) {
//判断当前Bean Field是否带有Inject注解
if (beanField.isAnnotationPresent(Inject.class)) {
//在Bean Map中获取Bean Field对应的实例
Class<?> beanFieldClass = beanField.getType();
Object beanFieldInstance = beanMap.get(beanFieldClass);
if (beanFieldInstance != null) {
//通过反射初始化BeanField的值
ReflectionUtil.setField(beanInstance, beanField, beanFieldInstance);
}
}
}
}
}
}
}
}

此时,在这个Ioc框架中所管理的对象都是单例的。IOC从BeanHelper中获取BeanMaP,而BeanMap中的对象都是事先创建好,放入这个Bean容器的。

加载controller

思路:通过ClassHelper,我们可以获取所有定义了Controller注解的类,可以通过反射获取该类中所有带有Action注解的方法(简称Action方法),获取action注解中的请求表达式,进而获取请求方法与请求路径,封装一个请求对象(request)与处理对象(Handler),最后将Request于Handler建立一个隐射关系,放入一个action Map中,并提供一个可根据请求方法与请求路径获取处理对象的方法。

ControllerHelper代码如下:

/**
* 控制器助手类
*/
public final class ControllerHelper { /**
* 用于存放请求与处理器的映射关系(简称Action Map)
*/
private static final Map<Request, Handler> ACTION_MAP = new HashMap<Request, Handler>(); static {
//获取所有的Controller类
Set<Class<?>> controllerClassSet = ClassHelper.getControllerClassSet();
if (CollectionUtil.isNotEmpty(controllerClassSet)) {
//遍历这些Controller类
for (Class<?> controllerClass : controllerClassSet) {
//获取Controller类中定义的方法
Method[] methods = controllerClass.getDeclaredMethods();
if (ArrayUtil.isNotEmpty(methods)) {
//遍历这些Controller类中定义的方法
for (Method method : methods) {
//判断当前方法中是否带有action注解
if (method.isAnnotationPresent(Action.class)) {
//从action注解中获取Url映射规则
Action action = method.getAnnotation(Action.class);
String mapping = action.value();
//验证Url映射规则
if (mapping.matches("\\w+:/\\w*")) {
String[] array = mapping.split(":");
if (ArrayUtil.isNotEmpty(array) && array.length == 2) {
//获取请求方法与请求路径
String requestMethod = array[0];
String requestPath = array[1];
Request request = new Request(requestMethod, requestPath);
Handler handler = new Handler(controllerClass, method);
//初始化Action Map
ACTION_MAP.put(request, handler);
}
}
}
}
}
}
}
} /**
* 获取 Handler
*/
public static Handler getHandler(String requestMethod, String requestPath) {
Request request = new Request(requestMethod, requestPath);
return ACTION_MAP.get(request);
}
}

初始化框架

我们创建了ClassHelper、BeanHelper、IOCHelper、ControllerHelper,这四个Helper类需要通过一个入口来加载他们,实际上就是加载他们的静态代码块。

/**
* 加载相应的 Helper 类
*/
public final class HelperLoader { public static void init() {
Class<?>[] classList = {
ClassHelper.class,
BeanHelper.class,
AopHelper.class,
IocHelper.class,
ControllerHelper.class
};
for (Class<?> cls : classList) {
ClassUtil.loadClass(cls.getName());
}
}
}

现在就可以直接调用HelperLoader的init方法来加载这些Helper了,这里只是为了加载更集中。

请求转发器

以上过程都是在为这一步做准备。我们现在需要写一个Servlet,让他处理所有的请求。

思路:从HttpServletRequest对象中获取请求方法与请求路径,通过ControllerHelper的getHandler方法来获取handler对象。当拿到Handler对象后,我们可以方便的获取Controller的类,通过BeanHelper的getBean方法获取Controller的实例对象。随后可以从HttpServletRequest对象中的所有请求参数,并将其初始化到一个param的对象中。

在Param中会有一系列的get方法,可通过参数名获取指定类型的参数值,也可以获取所有参数的map结构。

还可从Handler对象中获取Action的方法返回值,该返回值可能有两种情况:

(1)若返回值是View类型的视图对象,则返回一个jsp页面。

(2)若返回值是Data类型的数据对象,则返回一个JSON数据。

一下便是MVC框中最核心的DispatcherServlet类,代码如下:

/**
* 请求转发器
*/
@WebServlet(urlPatterns = "/*", loadOnStartup = 0)
public class DispatcherServlet extends HttpServlet { @Override
public void init(ServletConfig servletConfig) throws ServletException {
//初始化相关的Helper类
HelperLoader.init();
//获取ServletContext对象(用于注册Servlet)
ServletContext servletContext = servletConfig.getServletContext();
//注册Servlet
registerServlet(servletContext); UploadHelper.init(servletContext);
} private void registerServlet(ServletContext servletContext) {
//注册处理JSP的servlet
ServletRegistration jspServlet = servletContext.getServletRegistration("jsp");
jspServlet.addMapping("/index.jsp");
jspServlet.addMapping(ConfigHelper.getAppJspPath() + "*");
//注册处理静态资源的默认的servlet
ServletRegistration defaultServlet = servletContext.getServletRegistration("default");
defaultServlet.addMapping("/favicon.ico");
defaultServlet.addMapping(ConfigHelper.getAppAssetPath() + "*");
} @Override
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletHelper.init(request, response);
try {
//获取请求方法与请求路径
String requestMethod = request.getMethod().toLowerCase();
String requestPath = request.getPathInfo();
//获取Action处理器
Handler handler = ControllerHelper.getHandler(requestMethod, requestPath);
if (handler != null) {
//获取Controller类机器Bean实例
Class<?> controllerClass = handler.getControllerClass();
Object controllerBean = BeanHelper.getBean(controllerClass);
//创建请求参数对象
Param param;
if (UploadHelper.isMultipart(request)) {
param = UploadHelper.createParam(request);
} else {
param = RequestHelper.createParam(request);
}
//调用Action方法
Object result;
Method actionMethod = handler.getActionMethod();
if (param.isEmpty()) {
result = ReflectionUtil.invokeMethod(controllerBean, actionMethod);
} else {
result = ReflectionUtil.invokeMethod(controllerBean, actionMethod, param);
}
//处理Action返回值
if (result instanceof View) {
handleViewResult((View) result, request, response);
} else if (result instanceof Data) {
handleDataResult((Data) result, response);
}
}
} finally {
ServletHelper.destroy();
}
} private void handleViewResult(View view, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String path = view.getPath();
if (StringUtil.isNotEmpty(path)) {
if (path.startsWith("/")) {
response.sendRedirect(request.getContextPath() + path);
} else {
Map<String, Object> model = view.getModel();
for (Map.Entry<String, Object> entry : model.entrySet()) {
request.setAttribute(entry.getKey(), entry.getValue());
}
request.getRequestDispatcher(ConfigHelper.getAppJspPath() + path).forward(request, response);
}
}
} private void handleDataResult(Data data, HttpServletResponse response) throws IOException {
Object model = data.getModel();
if (model != null) {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
String json = JsonUtil.toJson(model);
writer.write(json);
writer.flush();
writer.close();
}
}
}

通过这个DispatcherServlet来处理所有的请求,根据请求信息从ControllerHelper中获取对应的Action方法,然后通过反射技术调用Action方法,同时需要具体的传入方法参数,最后拿到返回值并判断返回值的类型,进行相应的处理。

总结

在这一章,搭建了一个简单的MVC框架,定义了一系列的注解;通过一系列的Helper类来初始化MVC框架;通过DispatcherServlet来处理所有的请求;根据请求方法和请求路径来来调用具体的Action方法,判断Action方法的返回值,若为View类型,则调转到JSP页面,若为Data类型,则返回json数据。

在这一章中,学习了jdk中的类加载器、注解,对Java的认识又更深一步,期间感谢在黄老师创建的群里耐心回答我的问题的朋友。

代码下载

架构探险——第三章(搭建轻量级Java Web框架)的更多相关文章

  1. [转]轻量级 Java Web 框架架构设计

    工作闲暇之余,我想设计并开发一款轻量级 Java Web 框架,看看能否取代目前最为流行的而又越来越重的 Spring.Hibernate 等框架.请原谅在下的大胆行为与不自量力,本人不是为了重造轮子 ...

  2. Smart Framework:轻量级 Java Web 框架

    Smart Framework:轻量级 Java Web 框架 收藏 黄勇   工作闲暇之余,我开发了一款轻量级 Java Web 框架 —— Smart Framework. 开发该框架是为了: 加 ...

  3. 架构探险笔记3-搭建轻量级Java web框架

    MVC(Model-View-Controller,模型-视图-控制器)是一种常见的设计模式,可以使用这个模式将应用程序进行解耦. 上一章我们使用Servlet来充当MVC模式中的Controller ...

  4. 读《架构探险——从零开始写Java Web框架》

    内容提要 <架构探险--从零开始写Java Web框架>首先从一个简单的 Web 应用开始,让读者学会如何使用 IDEA.Maven.Git 等开发工具搭建 Java Web 应用:接着通 ...

  5. maven Spring+Spring MVC+Mybatis+mysql轻量级Java web开发环境搭建

    之前一直在做的一个GIS系统项目,采用了jsp+servlet框架,数据传输框架采用了apache的thrift框架,短时多传的风格还不错,但是较其他的java web项目显得有点太臃肿了,现在给大家 ...

  6. Intellij IDEA采用Maven+Spring MVC+Hibernate的架构搭建一个java web项目

    原文:Java web 项目搭建 Java web 项目搭建 简介 在上一节java web环境搭建中,我们配置了开发java web项目最基本的环境,现在我们将采用Spring MVC+Spring ...

  7. 【EatBook】-NO.3.EatBook.3.JavaArchitecture.2.001-《架构探险:从零开始写Java Web框架》-

    1.0.0 Summary Tittle:[EatBook]-NO.3.EatBook.3.JavaArchitecture.2.001-<架构探险:从零开始写Java Web框架>- S ...

  8. 初识轻量级Java开源框架 --- Spring

    初识轻量级Java开源框架 --- Spring 作者:egg 微博:http://weibo.com/xtfggef 出处:http://blog.csdn.net/zhangerqing spri ...

  9. 轻量级Java持久化框架,Hibernate完美助手,Minidao 1.6.2版本发布

    Minidao 1.6.2 版本发布,轻量级Java持久化框架(Hibernate完美助手) Minidao产生初衷? 采用Hibernate的J2EE项目都有一个痛病,针对复杂业务SQL,hiber ...

随机推荐

  1. 【Python】装饰器实现日志记录

    好的日志对一个软件的重要性是显而易见的.如果函数的入口都要写一行代码来记录日志,这种方式实在是太低效了,但一直没有找到更好的方法.后来用python写一些软件,了解到python的装饰器功能时,突然人 ...

  2. 使用CALayer实现图像镜面效果

    在iOS中,可以使用QuartzCore.framework基于CALayer做一些图像效果,不清楚CALayer,请先看这篇. 在这里我们给图像做一个简单的镜面反射效果,要学习一些图像变化的知识,首 ...

  3. 省市区县三级联动JAVA+MySQL+JQuery

    场景介绍: 由于项目要求,需要做一个进件(新增)功能,而该功能里要用到车品牌.车系.车型的联动查询,也就是经典的三级联动. 大体思路如下: 进入页面,会把所有的车品牌(第一级)在后台查询出来,放到缓存 ...

  4. elastic不错的官方文档(中文)

    https://www.blog-china.cn/template/documentHtml/1484101683485.html http://www.open-open.com/doc/list ...

  5. jquery操作select2控件

    (一)select2常用的操作:添加.移除.获取选中的value()与text() (1)移除事件:$("#select_id").unbind("change" ...

  6. android 蓝牙SPP协议通信

    准备 1.蓝牙串行端口基于SPP协议(Serial Port Profile),能在蓝牙设备之间创建串口进行数据传输 2.SPP的UUID:00001101-0000-1000-8000-00805F ...

  7. SpringBoot环境属性占位符解析和类型转换

    前提 前面写过一篇关于Environment属性加载的源码分析和扩展,里面提到属性的占位符解析和类型转换是相对复杂的,这篇文章就是要分析和解读这两个复杂的问题.关于这两个问题,选用一个比较复杂的参数处 ...

  8. Creating, detaching, re-attaching, and fixing a SUSPECT database

    今天遇到一个问题:一个数据库suspect了.然后又被用户detach了. 1,尝试将数据库attach回去,因为log file损坏失败了. 2,尝试将数据库attach回去,同一时候rebuild ...

  9. ROC

    # -*- coding: utf-8 -*- # __author__ = "JieYao" from biocluster.agent import Agent from bi ...

  10. top命令的Load average 含义及性能参考基值

    $ uptime11:12:26 up 3:44, 4 users, load average: 0.38, 0.31, 0.19 系统平均负载被定义为在特定时间间隔内运行队列中的平均进程树.如果一个 ...