架构探险——第三章(搭建轻量级Java Web框架)
解决的问题
- servlet的数量会随业务功能的扩展而不断增加,我们有必要减少servlet的数量,交给controller处理,它负责调用service的相关方法,并将返回值放入request或response中。
- 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用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。
根据注解获取相应的类
之前我们已经可以通过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框架)的更多相关文章
- [转]轻量级 Java Web 框架架构设计
工作闲暇之余,我想设计并开发一款轻量级 Java Web 框架,看看能否取代目前最为流行的而又越来越重的 Spring.Hibernate 等框架.请原谅在下的大胆行为与不自量力,本人不是为了重造轮子 ...
- Smart Framework:轻量级 Java Web 框架
Smart Framework:轻量级 Java Web 框架 收藏 黄勇 工作闲暇之余,我开发了一款轻量级 Java Web 框架 —— Smart Framework. 开发该框架是为了: 加 ...
- 架构探险笔记3-搭建轻量级Java web框架
MVC(Model-View-Controller,模型-视图-控制器)是一种常见的设计模式,可以使用这个模式将应用程序进行解耦. 上一章我们使用Servlet来充当MVC模式中的Controller ...
- 读《架构探险——从零开始写Java Web框架》
内容提要 <架构探险--从零开始写Java Web框架>首先从一个简单的 Web 应用开始,让读者学会如何使用 IDEA.Maven.Git 等开发工具搭建 Java Web 应用:接着通 ...
- maven Spring+Spring MVC+Mybatis+mysql轻量级Java web开发环境搭建
之前一直在做的一个GIS系统项目,采用了jsp+servlet框架,数据传输框架采用了apache的thrift框架,短时多传的风格还不错,但是较其他的java web项目显得有点太臃肿了,现在给大家 ...
- Intellij IDEA采用Maven+Spring MVC+Hibernate的架构搭建一个java web项目
原文:Java web 项目搭建 Java web 项目搭建 简介 在上一节java web环境搭建中,我们配置了开发java web项目最基本的环境,现在我们将采用Spring MVC+Spring ...
- 【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 ...
- 初识轻量级Java开源框架 --- Spring
初识轻量级Java开源框架 --- Spring 作者:egg 微博:http://weibo.com/xtfggef 出处:http://blog.csdn.net/zhangerqing spri ...
- 轻量级Java持久化框架,Hibernate完美助手,Minidao 1.6.2版本发布
Minidao 1.6.2 版本发布,轻量级Java持久化框架(Hibernate完美助手) Minidao产生初衷? 采用Hibernate的J2EE项目都有一个痛病,针对复杂业务SQL,hiber ...
随机推荐
- (转)Android项目重构之路:实现篇
前两篇文章Android项目重构之路:架构篇和Android项目重构之路:界面篇已经讲了我的项目开始搭建时的架构设计和界面设计,这篇就讲讲具体怎么实现的,以实现最小化可用产品(MVP)的目标,用最简单 ...
- [Android-Demo]Android View的拖动
↑ 请善用目录 Demo下载地址:http://download.csdn.net/detail/u011634756/5959637 (免积分哦~) ------------------------ ...
- 让你的Photoshop编辑制作ICO格式图标文件(ICOFormat支持图标文件插件)
相信非常多制图的朋友都喜欢用PS,可是你能用Photoshop保存为ICO格式图标文件吗?默认肯定不行.不知道是什么原因,大名鼎鼎的图像编辑软件Adobe Photoshop一直不支持导入导出ico格 ...
- Windows注册表中修改CMD默认路径
一.开启注册表“win键+R键”并输入regedit 二.在注册表项 HKEY_CURRENT_USER\ Software\ Microsoft\ Command Processor 新建一个项,并 ...
- 算法笔记_089:蓝桥杯练习 7-2求arccos值(Java)
目录 1 问题描述 2 解决方案 1 问题描述 问题描述 利用标准库中的cos(x)和fabs(x)函数实现arccos(x)函数,x取值范围是[-1, 1],返回值为[0, PI].要求结果准确 ...
- Swift的数组与OC中数组的区别
相同的值可以多次出现在一个数组的不同位置: Swift中的数组,数据值在被存储进入到某个数组之前类型必须明确,可以显示的类型标注或者类型推断.而且,Swift中的数组不必是对象类型. OC中的NSAr ...
- mybatis学习知识
目录 1,目录 2,介绍 3,快速入门 4,配置XML 5,xml文件映射 6,动态sql 7,java api 8,Statement Builders 9,日志 1,介绍 1.1 介绍 1.1.1 ...
- LaTeX 中换段落
中文文章中,LaTeX 中换段落: 在LaTeX 中,一个回车表示一个空格,两个回车表示一个分段.
- VS2012安装git
新安装了vs2012,尝试使用Git,安装过程比较简单,记录下来以备以后查阅. 1. 下载.安装Git 我的系统是Windows 7,需要安装Git for Windows. 下载地址: http ...
- asp.net,cookie,写cookie,取cookie(转载)
Cookie是一段文本信息,在客户端存储 Cookie 是 ASP.NET 的会话状态将请求与会话关联的方法之一.Cookie 也可以直接用于在请求之间保持数据,但数据随后将存储在客户端并随每个请求一 ...