MVC(Model-View-Controller,模型-视图-控制器)是一种常见的设计模式,可以使用这个模式将应用程序进行解耦。

上一章我们使用Servlet来充当MVC模式中的Controller。这样做虽然可以实现基本功能,但Servlet的数量会随着业务功能的拓展而不断增加。因此有必要减少Servlet的数量,将某类业务交给Controller来处理,它负责调用Service的相关方法,并将返回值放入Request或Response中。此外,Service不是通过new 的方式来创建的,而是通过一种名为“依赖注入”的方式,让框架为我们来创建所需要的对象。

目的:

1.快速搭建开发框架

2.加载并读取配置文件

3.实现一个简单的IOC的容器

4.加载指定的类

5.初始化框架

确定目标

打造一个轻量级MVC框架,而Controller是MVC的核心。我们想要的是如下代码:

@Controller
public class CustomerController { @Inject
private CustomerService customerService; /**
* 进入客户端界面
*/
@Action("get:/customer")
public View index(){
List<Customer> customerList = customerService.getCustomerList();
return new View("customer.jsp").addModel("customerList",customerList);
} /**
* 显示客户基本信息
*/
@Action("get:/customer_show")
public View show(Param param){
long id = param.getLong(id);
Customer customer = customerService.getCustomer(id);
return new View("customer_show.jsp").addModel("customer",customer);
} /**
* 删除客户信息
* @param param
* @return
*/
@Action("delete:/customer_edit")
public Data delete(Param param){
long id = param.getLong(id);
boolean result = customerService.deleteCustomer(id);
return new Data(result);
}
}

通过Controller注解来定义Controller类,在该类中,可通过Inject注解定义一系列Service成员变量,这就是“依赖注入”。此外,有一系列被Action注解所定义的方法(简称Action方法),在这些Action方法中,调用了Service成员变量的方法来完成具体的业务和逻辑。若返回View对象,则表示JSP页面;若返回Data对象,则表示一个JSON数据。

Controller代码非常清晰,一个Controller类包含了多个Action方法,可返回View或Data对象,分别对应JSP页面或JSON数据。

提示:在普通请求的情况下,可返回JSP页面;在Ajax请求的情况下,需要返回JSON数据。

创建框架项目

创建一个名为smart-framework的项目,它是一个普通的java项目,在pom.xml中需要添加Maven三坐标:

    <groupId>org.smart4j</groupId>
<artifactId>smart-framework</artifactId>
<version>1.0.</version>

因为该框架是Java Web框架,所以一定会依赖Servlet、JSP、JSTL。

        <!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.</version>
<scope>provided</scope>
</dependency>
<!--jsp-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<!--jstl-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>

在框架中会大量使用日志输出,最流行的日志框架就是Log4j了,但它只是日志的一种具体实现,如果将来需要使用其他更好的日志框架,那么代码中所有日志输出的地方都要修改。为了解决这个问题,我们使用一个名为SLF4J的日志框架,它实际上是日志框架的接口,而Log4J只是日志框架的一种实现而已。只需要添加以下依赖,就能同时引入SLF4J和Log4J两个依赖。

        <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.</version>
</dependency>

mysql驱动

        <!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.</version>
</dependency>

由于在Controller的Action方法返回值中是可以返回json数据的,因此需要选择一款JSON序列化工具,目前在功能、性能、稳定性各方面表现好的JSON序列化工具就是Jackson了。

        <!--Jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.</version>
</dependency>

常用的Apache Commons工具类

        <!--常用的两个Apache Commons工具类-->
<!--Apache Commons Lang-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.3.</version>
</dependency>
<!--Apache Commons Collections-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>

对于JDBC,我们选择了轻量级的DbUtils,它也是Apache Commons的项目之一。

        <!--Apache Commons DbUtils-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>

在框架中需要用到数据库连接池,我们选择了总和能力强的连接池框架DBCP,同样是Apache Commons项目之一。

        <!--Apache DBCP连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.0.</version>
</dependency>

除了smart-framework这个项目外,我们有必要再创建一个使用该框架的项目,名为chapter3,chapter3是一个java web项目,只需要依赖于smart-framework即可,详细的pom.xml如下

<!--Smart Framework-->
<dependency>
<groupId>org.smart4j</groupId>
<artificatId>smart-framework</artifactId>
<version>1.0.</version>
</dependency>

定义框架配置项

在chapter3项目的src/main/resources目录下,创建一个名为smart.properties的文件,内容如下

smart.framework.jdbc.driver=com.mysql.jdbc.Driver
smart.framework.jdbc.url=jdbc:mysql://localhost:/demo
smart.framework.jdbc.username=root
smart.framework.jdbc.password=root smart.framework.app.base_package=org.smart4j.chapter3 --扫描包
smart.framework.app.jsp_path=/WEB-INF/view/ --视图所在路径
smart.framework.app.asset_path=/asset/ --静态资源

在chapter3中添加引用

        <dependency>
<groupId>org.smart4j</groupId>
<artifactId>smart-framework</artifactId>
<version>1.0.0</version>
</dependency>

加载配置项

常用工具类

配置文件已经有了,下面要根据配置项的名称获取配置项的取值,只是框架需要做的事。我们在smart-framework项目中创建一个名为ConfigHelper的助手类,让它来读取smart.properties配置文件。

首先,创建一个名为ConfigConstant的常量类,用来维护配置文件中相关的配置项名称。

package org.smart4j.framework.helper;

/**
* @program: ConfigConstant
* @description: 相變量配置
**/
public interface ConfigConstant {
String CONFIG_FILE = "smart.properties";
String JDBC_DRIVER = "smart.framework.jdbc.driver";
String JDBC_URL = "smart.framework.jdbc.url";
String JDBC_USERNAME = "smart.framework.jdbc.username";
String JDBC_PASSWORD = "smart.framework.jdbc.password"; String APP_BASE_PACKAGE = "smart.framework.app.base_package";
String APP_JSP_PATH = "smart.framework.app.jsp_path";
String APP_ASSET_PATH = "smart.framework.app.asset_path";
}

然后借助PropsUtil工具类实现ConfigHelper类,此类中是一些静态方法,分别获取smart.properties配置文件中的配置项。

package org.smart4j.framework.helper;

import org.smart4j.framework.PropsUtil;
import java.util.Properties; /**
* @program: ConfigHelper
* @description: 属性文件助手类
**/
public class ConfigHelper { private static final Properties CONFIG_PROPS = PropsUtil.loadProps(ConfigConstant.CONFIG_FILE); /**
* 獲取JDBC驅動
* @return
*/
public static String getJdbcDriver(){
return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_DRIVER);
} /**
* 獲取JDBC URL
* @return
*/
public static String getJdbcUrl(){
return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_URL);
} /**
* 獲取JDBC 用戶名
* @return
*/
public static String getJdbcUsername(){
return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_USERNAME);
} /**
* 獲取JDBC 密碼
* @return
*/
public static String getJdbcPassword(){
return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_PASSWORD);
} /**
* 獲取應用基礎包名
* @return
*/
public static String getAppBasePackage(){
return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_BASE_PACKAGE);
} /**
* 獲取應用JSP路徑
* @return
*/
public static String getAppJspPath(){
return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_JSP_PATH,"/WEB-INF/view/");
} /**
* 獲取應用靜態資源路徑
* @return
*/
public static String getAppAssetPath(){
return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_ASSET_PATH,"/asset/");
} }

在ConfigHelper类中,为smart.framework.app.jsp_path与smart.framework.app.asset_path配置项提供了默认值。也就是说smart.properties配置文件中这两个配置项是可选的,如果不是特殊要求,可以修改这里两个配置。换句话说,这两个配置会以smart.properties配置文件中所配置的值为优先值。

开发一个类加载器

开发一个类加载器用来加载该基础包名下的所有类,例如:使用了注解的类、实现了某接口的类、继承了某父类的所有子类等。

写一个ClassUtil工具类,提供与类操作相关的方法,比如获取类加载器、加载类、获取指定包名下的所有类。

package org.smart4j.framework.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smart4j.framework.StringUtil; import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile; /**
* @program: ClassUtil
* @description: 類加載器
* @author: qiuyu
* @create: 2018-09-11 19:00
**/
public class ClassUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class); /**
* 獲取類加載器
* 給下面的加載類用
* @return
*/
public static ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
} /**
* 加載類
* 将类加载到方法区中
* @param className 类名 packageName.className
* @param isInitialized 是否初始化静态代码块和静态字段
* @return
*/
public static Class<?> loadClass(String className,boolean isInitialized){
Class<?> cls;
try {
//className為類全名,isInitialized為是否初始化靜態代碼塊和靜態字段
cls = Class.forName(className,isInitialized,getClassLoader());
} catch (ClassNotFoundException e) {
LOGGER.error("load class failure",e);
throw new RuntimeException(e);
// e.printStackTrace();
}
return cls;
} /**
* 獲取指定包名下的所有類文件/文件夹和jar包
* @param packageName
* @return
*/
public static Set<Class<?>> getClassSet(String packageName){
Set<Class<?>> classSet = new HashSet<Class<?>>();
try {
//获取资源的url枚举
Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
while(urls.hasMoreElements()){ //如果存在该包
URL url = urls.nextElement(); //获取包的url
if (url!=null){
String protocol = url.getProtocol(); //查看url的协议(file、http)
if (protocol.equals("file")){ //如果是文件或者文件夹
String packagePath = url.getPath().replaceAll("%20"," "); //空格的转义字符替换为空格,这个是java一个历史悠久的bug
addClass(classSet,packagePath,packageName);
}else if (protocol.equals("jar")){ //如果是jar包
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); //根据url获取jar的Connection
if (jarURLConnection!=null){
JarFile jarFile = jarURLConnection.getJarFile(); //根据connection获取jar文件
if (jarFile!=null){
Enumeration<JarEntry> jarEntries = jarFile.entries(); //获取jar文件中的实体枚举
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 (IOException e) {
e.printStackTrace();
}
return classSet;
} /**
* 加载指定包下面的jar和class文件
* @param classSet 存class的集合
* @param packagePath 包的真实路径 D:***
* @param packageName 包名 *.*.*
*/
private static void addClass(Set<Class<?>> classSet,String packagePath,String packageName){
//System.out.println(packageName);
//System.out.println(packagePath);
//获得包下面的所有文件(.class和文件夹)
File[] files = new File(packagePath).listFiles(new FileFilter() {
//文件过滤器,只获取.class结尾的文件和文件将夹
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; //根据传进来的包名packageName和获取的.class文件名拼接成packageName.className
doAddClass(classSet,className);
}
}else { //如果是目录
String subPackagePath = fileName; //子文件夹名
if (StringUtil.isNotEmpty(packagePath)){
subPackagePath=packagePath+"/"+subPackagePath; //子目录路径
} String subPackageName = fileName;
if (StringUtil.isNotEmpty(subPackageName)){
subPackageName=packageName+"."+subPackageName; //子包名
}
addClass(classSet,subPackagePath,subPackageName); // 将子包和子包路径传进去
}
}
} /**
* 将类加载到方法区,并放入Set集合中
* @param classSet 存被加载类的集合
* @param className 类的全名 packageName.className
*/
private static void doAddClass(Set<Class<?>> classSet,String className){
Class<?> cls = loadClass(className,false);
classSet.add(cls);
} public static void main(String[] args) {
//loadClass("org.smart4j.framework.util.Cls",false);
//Cls cls = new Cls();
//加載環境中的jar包中的報名
Set<Class<?>> classSet = getClassSet("org.smart4j.framework.util");
System.out.println(classSet);
}
}

注解

为了实现在控制器上使用Controller注解,在控制器类的方法上使用Action注解,在服务类上使用Service注解,在控制器类中使用Inject注解将服务类依赖注入进来。因此需要四个注解类。

控制器注解代码

package org.smart4j.framework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 控制器注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller { }

Action方法注解代码

/**
* 控制器注解
* Action方法注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
/**
* 请求类型与路径
*/
String value();
}

服务类注解代码

/**
* 服务类注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}

依赖注入注解代码

/**
* 依赖注入注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
}

在smart.properties配置文件中指定了smart.framework.app.base_package,它是整个应用的基础包名,通过ClassUtil加载的类都需要基于该基础包名。所以有必要提供一个ClassHelper助手类,让它分别获取应用包名下的所有类、应用宝名下所有Service类、应用包名下所有Controller类。此外,我们可以将带有Controller注解与Service注解的类所产生的对象,理解为由Smart框架所管理的Bean,所以有必要在ClassHelper类中增加一个获取应用包名下所有Bean类的方法。ClassHelper代码

/**
* @program: ClassHelper
* @description: 类操作助手
* 获取所有Controller和Service类的集合
**/
public class ClassHelper { /**
* 定义类集合
*/
private static final Set<Class<?>> CLASS_SET; static {
String basePackage = ConfigHelper.getAppBasePackage();
CLASS_SET = ClassUtil.getClassSet(basePackage);
} /**
* 获取应用包名下的所有类
* @return
*/
public static Set<Class<?>> getClassSet(){
return CLASS_SET;
} /**
* 获取所有Controller类
* @return
*/
public static Set<Class<?>> getControllerClassSet(){
Set<Class<?>> classSet = new HashSet<Class<?>>();
for (Class<?> cls : CLASS_SET){
if (cls.isAnnotationPresent(Controller.class)){
classSet.add(cls);
}
}
return classSet;
} /**
* 获取所有Service类
* @return
*/
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类(Controller和Service)
* @return
*/
public static Set<Class<?>> getBeanClassSet(){
Set<Class<?>> beanClassSet = new HashSet<Class<?>>();
beanClassSet.addAll(getControllerClassSet());
beanClassSet.addAll(getServiceClassSet());
return beanClassSet;
}
}

实现Bean容器

使用ClassHelper类可以获取所加载的类,但是无法通过类来实例化对象。因此,需要提供一个反射工具类,让它封装Java反射相关的API,对外提供更好用的工具方法。命名为ReflectionUtil。

package org.smart4j.framework.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.lang.reflect.Field;
import java.lang.reflect.Method; /**
* 反射工具
* 实现bean容器
*/
public class ReflectionUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class); /**
* 创建实例
* @param cls 已经加载的类
* @return
*/
public static Object newInstance(Class<?> cls){
Object instance = null;
try {
instance = cls.newInstance();
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("new instance failure",e);
throw new RuntimeException(e);
}
return instance;
} /**
* 调用方法
* @param obj 调用方法的实例对象
* @param method 方法
* @param args 方法参数
* @return
*/
public static Object invokeMethod(Object obj, Method method,Object... args){
Object result = null;
try {
method.setAccessible(true);
result = method.invoke(obj,args);
} catch (Exception e) {
//e.printStackTrace();
LOGGER.error("invoke method failure",e);
throw new RuntimeException(e);
}
return result;
} /**
* 设置成员变量的值
* @param obj 实例对象
* @param field 字段
* @param value 字段值
*/
public static void setField(Object obj, Field field,Object value){
try {
field.setAccessible(true);
field.set(obj,value);
} catch (IllegalAccessException e) {
//e.printStackTrace();
LOGGER.error("set field failure",e);
throw new RuntimeException(e);
}
}
}

要获取所有被框架管理的Bean类,需要调用ClassHelper类的getBeanClassSet方法,然后循环调用ReflectionUtil类的newInstance方法,根据类来实例化对象,然后将每次创建的对象放在一个静态的Map<Class<?>,Object>中。我们需要随时获取该Map,还需要通过该Map的Key(类名)去获取所对应的value(Bean)对象。BeanHelper类代码如下:

package org.smart4j.framework.helper;

import org.smart4j.framework.util.ReflectionUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.Set; /**
* Bean助手类
*
*/
public class BeanHelper {
/**
* 定义bean映射(用于存放Bean类与Bean实例的映射关系)
*/
private static final Map<Class<?>,Object> BEAN_MAP = new HashMap<Class<?>, Object>(); static{
//获取所有Controller和Service
Set<Class<?>> beanClassSet = ClassHelper.getBeanClassSet();
//遍历所有的Controlller和Service类
for (Class<?> beanClass:beanClassSet){
Object obj = ReflectionUtil.newInstance(beanClass); //将类实例化
BEAN_MAP.put(beanClass,obj); //将类和实例放入Map中 Map<Class,Obj>
}
} /**
* 获取Bean映射
* @return
*/
public static Map<Class<?>, Object> getBeanMap() {
return BEAN_MAP;
} /**
* 根据Class获取bean实例
* @param cls bean实例所属的类
* @param <T> 类的实例对象
* @return
*/
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);
}
}

BeanHelper就相当于一个“Bean容器”了,因为在Bean Map中存放了Bean类与Bean实例的映射关系,我们只需通过调用getBean方法,传入一个Bean类,就能获取Bean实例。

实现依赖注入

Controller中定义的Service成员变量,在Controller的Action方法中调用Service成员变量的方法。这里Service并没有通过new的方式实例化,而是通过框架自身来实例化,像这类实例化过程,称为IOC(Inversion of Control,控制反转)。控制不是由开发者来决定的,而是反转给框架了。一般地,我们也将反转称为DI(Dependency Injection,依赖注入),可以理解为将某个类需要依赖的成员注入到这个类中。那么,如何来实现依赖注入呢?

最简单的方式是,先通过BeanHelper获取所有BeanMap(是一个Map<Class<?>>,Object结构,记录了类与对象的映射关系)。然后遍历这个映射关系,分别取出Bean类与Bean实例,进而通过反射获取类中所有的成员变量。继续遍历这些成员变量,在循环中判断当前成员变量是否带有Inject注解,若带有该注解,则从Bean Map 中根据Bean实例。最后通过ReflectionUtil#setField方法来修改当前成员变量的值。

package org.smart4j.framework.helper;

import org.smart4j.framework.ArrayUtil;
import org.smart4j.framework.annotation.Inject;
import org.smart4j.framework.util.ReflectionUtil; import java.lang.reflect.Field;
import java.util.Map; /**
* 依赖注入助手类
*/
public class IocHelper {
static {
//获取所有bean类与Bean实例类之间的关系的集合(简称BeanMap)
Map<Class<?>,Object> beanMap = BeanHelper.getBeanMap();
if (CollectionUtil.isNotEmpty(beanMap)){
//遍历beanMap
for (Map.Entry<Class<?>,Object> beanEntry:beanMap.entrySet()){
//从Bean中获取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){
//判断是否带有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);
}
}
}
}
}
}
}
}

只需要在IocHelper的静态代码块中实现相关逻辑,就能完成IOC容器的初始化工作。在IOCHelper这个类被加载的时候,就会加载它的静态块。后面我们需要找一个统一的地方来加载这个IocHelper。其中涉及了ArrayUtil类,代码如下

package org.smart4j.framework;

import org.apache.commons.lang3.ArrayUtils;

/**
* 数组工具类
*/
public class ArrayUtil {
/**
* 判断数组是否为空
* @param array
* @return
*/
public static boolean isNotEmpty(Object[] array){
return !isEmpty(array);
} /**
* 判断数组是否非空
* @param array
* @return
*/
public static boolean isEmpty(Object[] array){
return array==null||array.length==0;
}
}

需要注意的是,此时IOC框架中所管理的对象都是单例的,由于IOC框架底层还是从BeanHelper中获取的Bean Map的,而Bean Map中的对象都是事先创建好并放入这个Bean容器中的,所有的对象都是单例的。

加载Controller

我们需要创建一个ControllerHelper类,让它来处理如下逻辑:

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

Request类

package org.smart4j.framework.bean;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; /**
* 封装请求信息
*/
public class Request { /**
* 请求方法
*/
private String requestMethod; /**
* 请求路径
*/
private String requestPath; public Request(String requestMethod,String requestPath){
this.requestMethod = requestMethod;
this.requestPath = requestPath;
} public String getRequestMethod() {
return requestMethod;
} public void setRequestMethod(String requestMethod) {
this.requestMethod = requestMethod;
} public String getRequestPath() {
return requestPath;
} public void setRequestPath(String requestPath) {
this.requestPath = requestPath;
} @Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
} @Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this,obj);
}
}

Handler类

package org.smart4j.framework.bean;

import java.lang.reflect.Method;

/**
* @program: Handler
* @description: 封装Action信息
**/
public class Handler {
/**
* Controller类
*/
private Class<?> controllerClass; /**
* Action方法
*/
private Method actionMethod; public Handler(Class<?> controllerClass, Method actionMethod) {
this.controllerClass = controllerClass;
this.actionMethod = actionMethod;
} public Class<?> getControllerClass() {
return controllerClass;
} public Method getActionMethod() {
return actionMethod;
}
}

ControllerHelper类

package org.smart4j.framework.helper;

import org.smart4j.framework.ArrayUtil;
import org.smart4j.framework.annotation.Action;
import org.smart4j.framework.bean.Handler;
import org.smart4j.framework.bean.Request; import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; /**
* @program: ControllerHelper
* @description: 控制器助手类
* @author: QiuYu
* @create: 2018-09-27 15:34
**/
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 (org.smart4j.framework.helper.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
* @param requestMethod
* @param requestPath
* @return
*/
public static Handler getHandler(String requestMethod,String requestPath){
Request request = new Request(requestMethod,requestPath);
return ACTION_MAP.get(request);
}
}

在ControllerHelper中封装了一个ActionMap,通过它来存放Request和Handler之间的映射关系,然后通过ClassHelper来获取所有带有Controller注解的类,接着遍历这些Controller类,从Action注解中提取URL,最后初始化Request与Handler之间的映射关系。

初始化框架

通过上面的过程,我们创建了ClassHelper、BeanHelper、IOCHelper、ControllerHelper,这四个Helper类需要通过一个入口程序来加载他们。

package org.smart4j.framework;

import org.smart4j.framework.helper.BeanHelper;
import org.smart4j.framework.helper.ClassHelper;
import org.smart4j.framework.helper.ControllerHelper;
import org.smart4j.framework.helper.IocHelper;
import org.smart4j.framework.util.ClassUtil; /**
* @program: HelperLoader
* @description: 加载相应的Helper类
**/
public class HelperLoader {
public static void init(){
Class<?>[] classList={
ClassHelper.class,
BeanHelper.class,
IocHelper.class,
ControllerHelper.class
}; for (Class<?> cls:classList){
ClassUtil.loadClass(cls.getName(),true);
}
}
}

当我们第一次访问类时,就会加载其static块。这里只是为了让它们加载更集中,所以才写了一个HelperLoader类。

请求转发器

以上所有过程都为这一步做准备,现在需要实现一个Servlet,让它来处理所有的请求。从HttpServletRequest对象中获取请求方法与请求路径,通过ControllerHelper#getHandler方法来获取。

当拿到Handler对象后,我们可以获取Controller类,进而通过BeanHelper.个体Bean方法获取Controller的实例对象。

随后可以从HttpServletRequest对象中获取所有请求参数,并将其初始化到一个名为Param的对象中

Param参数类代码:

package org.smart4j.framework.bean;

import org.smart4j.framework.CastUtil;

import java.util.Map;

/**
* @program: Param
* @description: 请求参数对象
* @author: Created by Autumn
* @create: 2018-10-24 10:47
*/ public class Param {
private Map<String,Object> paramMap; public Param(Map<String, Object> paramMap) {
this.paramMap = paramMap;
} /**
* 根据参数名获取long型参数值
* @param name
* @return
*/
public long getLong(String name){
return CastUtil.castLong(paramMap.get(name));
} /**
* 获取所有字段信息
* @return
*/
public Map<String,Object> getMap(){
return paramMap;
}
}

Param可以通过参数名获取指定类型的参数值,也可以获取所有参数的Map结构。

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

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

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

View类

package org.smart4j.framework.bean;

import java.util.Map;

/**
* @program: View
* @description: 视图对象
*/ public class View {
/**
* 视图路径
*/
private String path; /**
* 模型数据
*/
private Map<String,Object> model = new HashMap<String,Object>(); public View(String path, Map<String, Object> model) {
this.path = path;
this.model = model;
} public View addModel(String key,Object value){
model.put(key,value);
return this;
} public String getPath() {
return path;
} public Map<String, Object> getModel() {
return model;
}
}

Data类

package org.smart4j.framework.bean;

/**
* @program: Data
* @description: 返回数据对象
*/ public class Data {
/**
* 模型数据
*/
private Object model; public Data(Object model) {
this.model = model;
} public Object getModel() {
return model;
}
}

返回的Data类型的数据封装了一个Object类型的模型数据,框架会将该对象写入HttpServletResponse对象中,从而直接输出至浏览器。

MVC框架中核心的DispatcherServlet类

package org.smart4j.framework;

import org.smart4j.framework.bean.Data;
import org.smart4j.framework.bean.Handler;
import org.smart4j.framework.bean.Param;
import org.smart4j.framework.bean.View;
import org.smart4j.framework.helper.BeanHelper;
import org.smart4j.framework.helper.ConfigHelper;
import org.smart4j.framework.helper.ControllerHelper;
import org.smart4j.framework.util.CodecUtil;
import org.smart4j.framework.util.JsonUtil;
import org.smart4j.framework.util.ReflectionUtil;
import org.smart4j.framework.util.StreamUtil; import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map; /**
* @program: DispatcherServlet
* @description: 请求转发器
* @author: Created by Autumn
* @create: 2018-10-24 11:34
*/ @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();
//注册处理JSP的Servlet
ServletRegistration jspServlet = servletContext.getServletRegistration("jsp");
jspServlet.addMapping(ConfigHelper.getAppJspPath()+"*");
//注册处理静态资源的默认Servlet
ServletRegistration defaultServlet = servletContext.getServletRegistration("default");
defaultServlet.addMapping(ConfigHelper.getAppAssetPath()+"*");
} @Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求方法与请求路径
String requestMethod = req.getMethod().toLowerCase();
String requestPath = req.getPathInfo();
//获取Action处理器
Handler handler= ControllerHelper.getHandler(requestMethod,requestPath);
if(handler!=null){
//获取Controller类机器Bean实例
Class<?> controllerClass = handler.getControllerClass();
Object controllerBean = BeanHelper.getBean(controllerClass);
//创建请求参数对象
Map<String,Object> paramMap = new HashMap<String, Object>();
Enumeration<String> paramNames = req.getParameterNames();
while(paramNames.hasMoreElements()){
String paramName = paramNames.nextElement();
String paramValue = req.getParameter(paramName);
paramMap.put(paramName,paramValue);
}
//获取请求body中的参数
String body = CodecUtil.dencodeURL(StreamUtil.getString(req.getInputStream()));
if (StringUtil.isNotEmpty(body)){
String[] params = StringUtil.splitString(body,"&");
if (ArrayUtil.isNotEmpty(params)){
for (String param:params){
String[] array = StringUtil.splitString(param,"=");
if (ArrayUtil.isNotEmpty(array)&&array.length==2){
String paramName = array[0];
String paramValue = array[1];
paramMap.put(paramName,paramValue);
}
}
}
}
Param param = new Param(paramMap);
//调用Action方法
Method actionMethod = handler.getActionMethod();
Object result = ReflectionUtil.invokeMethod(controllerBean,actionMethod,param);
//处理Action方法返回值
if (result instanceof View){
//返回JSP页面
View view = (View) result;
String path = view.getPath();
if (StringUtil.isNotEmpty(path)){
if (path.startsWith("/")){ //这里应该是判断是否有数据
resp.sendRedirect(req.getContextPath()+path);
} else {
Map<String,Object> model = view.getModel();
for (Map.Entry<String,Object> entry:model.entrySet()){
req.setAttribute(entry.getKey(),entry.getValue());
}
req.getRequestDispatcher(ConfigHelper.getAppJspPath()+path).forward(req,resp);
} }
}else if (result instanceof Data){
//返回Json数据
Data data = (Data) result;
Object model = data.getModel();
if (model!=null){
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
PrintWriter writer = resp.getWriter();
String json = JsonUtil.toJson(model);
writer.write(json);
writer.flush();
writer.close();
}
}
}
}
}

其中涉及到流工具类、编码工具类、Json工具类

一款简单的MVC框架开发完毕,通过这个DispatcherServlet来处理所有的请求,根据请求信息从ControllerHelper中获取对应的Action方法,然后使用反射技术调用Action方法,同事需要传入具体的方法参数,最后拿到返回值的类型并进行相应的处理。

安装到Maven本地仓库

1.用Maven将项目打包成jar

2.用Maven将jar包安装到仓库

此种方式的jar包不会把依赖jar包打包进去

mvn install:install-file -Dfile=smart-framework-1.0..jar -DgroupId=org.smart4j -DartifactId=smart-framework -Dversion=1.0. -Dpackaging=jar

此种为IDEA中安装包的代码

java -Dmaven.multiModuleProjectDirectory=F:\intellijIDEAworkspace\smart-framework -Dmaven.home=D:\Programmer_QY\apache-maven-3.5. -Dclassworlds.conf=D:\Programmer_QY\apache-maven-3.5.\bin\m2.conf "-javaagent:D:\Programmer_QY\IntelliJ IDEA 2017.1.1\lib\idea_rt.jar=51430:D:\Programmer_QY\IntelliJ IDEA 2017.1.1\bin" -Dfile.encoding=UTF-8 -classpath D:\Programmer_QY\apache-maven-3.5.0\boot\plexus-classworlds-2.5.2.jar org.codehaus.classworlds.Launcher -Didea.version=2017.1.1 -s D:\Programmer_QY\apache-maven-3.5.0\conf\settings.xml install

相当于以下两步

总结

通过Controller注解来定义Controller类;通过Inject注解来实现依赖注入;通过Action注解来定义Action方法。通过一系列的Helper类来初始化MVC框架;通过DispatcherServlet来处理所有的请求;根据请求方法与请求路径来调用具体的Action方法,判断Action方法的返回值,若为View类型,则跳转到JSP页面,若为Data类型,则返回JSON数据。

整个框架基本能跑起来了,但里面还存在大量需要优化的地方。此外,还有一些非常好的特性尚未提供,比如AOP(Aspect Oriented Programming,面向方面编程)。我们可以使用这个特性来实现一些横向拦截操作,比如性能分析、日志收集、安全监控等,下一章我们将介绍如何实现AOP特性。

结果截图

源码:框架框架使用项目示例

架构探险笔记3-搭建轻量级Java web框架的更多相关文章

  1. 架构探险——第三章(搭建轻量级Java Web框架)

    解决的问题 servlet的数量会随业务功能的扩展而不断增加,我们有必要减少servlet的数量,交给controller处理,它负责调用service的相关方法,并将返回值放入request或res ...

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

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

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

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

  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. 架构探险笔记12-安全控制框架Shiro

    什么是Shiro Shiro是Apache组织下的一款轻量级Java安全框架.Spring Security相对来说比较臃肿. 官网 Shiro提供的服务 1.Authentication(认证) 2 ...

  9. JAVA web 框架集合

    “框架”犹如滔滔江水连绵不绝, 知道有它就好,先掌握自己工作和主流的框架: 在研究好用和新框架. 主流框架教程分享在Java帮帮-免费资源网 其他教程需要时间制作,会陆续分享!!! 152款框架,你还 ...

随机推荐

  1. uniGUI试用笔记(八)

    在业务系统中常常使用回车键(Enter)替代Tab键完成焦点跳转,在uniGUI下,可以不用代码,直接使用TUniForm的NavigateKeys进行设置: 其中Next和Prior决定了焦点向下一 ...

  2. uniGUI试用笔记(一)

    通过向导创建一个uniGUI应用服务器,工程中有三个文件: TUniServerModule = class(TUniGUIServerModule) TUniMainModule = class(T ...

  3. 比酒量|2012年蓝桥杯B组题解析第三题-fishers

    (5')比酒量 有一群海盗(不多于20人),在船上比拼酒量.过程如下:打开一瓶酒,所有在场的人平分喝下,有几个人倒下了.再打开一瓶酒平分,又有倒下的,再次重复...... 直到开了第4瓶酒,坐着的已经 ...

  4. 【教程】Git在Eclipse中的安装和基本使用

    一.安装 点击 Help->Install New Software->add 安装地址为:http://download.eclipse.org/egit/updates/ 选择插件   ...

  5. 【Spring Security】一、快速入手

    一 概要 Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架.它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权.这 ...

  6. How to know the directory size in CENTOS 查看文件夹大小

    Under any linux system, you want to use the command du. (Disk Usage) Common usage is : du -sh file(s ...

  7. dp专题练习

    顺便开另外一篇放一些学过的各种dp dp总结:https://www.cnblogs.com/henry-1202/p/9194066.html 开坑先放15道题,后面慢慢补 目标50道题啦~~,目前 ...

  8. P2522 [HAOI2011]Problem b

    还有三倍经验的吗(窒息) 思路 其实就是P3455套了个简单的容斥 把问题转化成f(n,m,k)-f(a-1,m,k)-f(n,b-1,k)+f(a-1,b-1,k)就可以了 和p3455几乎一样的代 ...

  9. lvs笔记

    LVS是Linux Virtual Server的简写,意为Linux虚拟服务器,是虚拟的服务器集群系统,可在UNIX/LINUX平台下实现负载均衡集群功能.该项目在1998年5月由章文嵩博士组织成立 ...

  10. Java 虚拟机 最易理解的 全面解析

    先上一个最容易理解的类实例化的内存模型案例截图: 转载自:https://www.zybuluo.com/Yano/note/321063 周志明著的<深入理解 Java 虚拟机>的干货~ ...