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

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

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

目的:

1.快速搭建开发框架

2.加载并读取配置文件

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

4.加载指定的类

5.初始化框架

确定目标

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

@Controllerpublic classCustomerController {

@InjectprivateCustomerService customerService;/*** 进入客户端界面*/@Action("get:/customer")publicView index(){

List customerList =customerService.getCustomerList();return new View("customer.jsp").addModel("customerList",customerList);

}/*** 显示客户基本信息*/@Action("get:/customer_show")publicView show(Param param){long id =param.getLong(id);

Customer customer=customerService.getCustomer(id);return new View("customer_show.jsp").addModel("customer",customer);

}/*** 删除客户信息

*@paramparam

*@return

*/@Action("delete:/customer_edit")publicData delete(Param param){long id =param.getLong(id);boolean result =customerService.deleteCustomer(id);return newData(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三坐标:

org.smart4j

smart-framework

1.0.0

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

javax.servlet

javax.servlet-api

3.1.0

provided

javax.servlet.jsp

jsp-api

2.2

provided

javax.servlet

jstl

1.2

runtime

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

org.slf4j

slf4j-log4j12

1.7.25

mysql驱动

mysql

mysql-connector-java

5.1.33

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

com.fasterxml.jackson.core

jackson-databind

2.4.4

常用的Apache Commons工具类

org.apache.commons

commons-lang3

3.3.2

org.apache.commons

commons-collections4

4.0

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

commons-dbutils

commons-dbutils

1.6

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

org.apache.commons

commons-dbcp2

2.0.1

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

org.smart4j

smart-framework

1.0.0

定义框架配置项

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

smart.framework.jdbc.driver=com.mysql.jdbc.Driver

smart.framework.jdbc.url=jdbc:mysql://localhost:3306/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中添加引用

org.smart4j

smart-framework

1.0.0

加载配置项

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

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

packageorg.smart4j.framework.helper;/*** @program: ConfigConstant

* @description: 相變量配置

**/

public interfaceConfigConstant {

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配置文件中的配置项。

packageorg.smart4j.framework.helper;importorg.smart4j.framework.PropsUtil;importjava.util.Properties;/*** @program: ConfigHelper

* @description: 属性文件助手类

**/

public classConfigHelper {private static final Properties CONFIG_PROPS =PropsUtil.loadProps(ConfigConstant.CONFIG_FILE);/*** 獲取JDBC驅動

*@return

*/

public staticString getJdbcDriver(){returnPropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_DRIVER);

}/*** 獲取JDBC URL

*@return

*/

public staticString getJdbcUrl(){returnPropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_URL);

}/*** 獲取JDBC 用戶名

*@return

*/

public staticString getJdbcUsername(){returnPropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_USERNAME);

}/*** 獲取JDBC 密碼

*@return

*/

public staticString getJdbcPassword(){returnPropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_PASSWORD);

}/*** 獲取應用基礎包名

*@return

*/

public staticString getAppBasePackage(){returnPropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_BASE_PACKAGE);

}/*** 獲取應用JSP路徑

*@return

*/

public staticString getAppJspPath(){return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_JSP_PATH,"/WEB-INF/view/");

}/*** 獲取應用靜態資源路徑

*@return

*/

public staticString 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工具类,提供与类操作相关的方法,比如获取类加载器、加载类、获取指定包名下的所有类。

packageorg.smart4j.framework.util;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.smart4j.framework.StringUtil;importjava.io.File;importjava.io.FileFilter;importjava.io.IOException;importjava.net.JarURLConnection;importjava.net.URL;importjava.util.Enumeration;importjava.util.HashSet;importjava.util.Set;importjava.util.jar.JarEntry;importjava.util.jar.JarFile;/*** @program: ClassUtil

* @description: 類加載器

*@author: qiuyu

* @create: 2018-09-11 19:00

**/

public classClassUtil {private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);/*** 獲取類加載器

* 給下面的加載類用

*@return

*/

public staticClassLoader getClassLoader(){returnThread.currentThread().getContextClassLoader();

}/*** 加載類

* 将类加载到方法区中

*@paramclassName 类名 packageName.className

*@paramisInitialized 是否初始化静态代码块和静态字段

*@return

*/

public static Class> loadClass(String className,booleanisInitialized){

Class>cls;try{//className為類全名,isInitialized為是否初始化靜態代碼塊和靜態字段

cls =Class.forName(className,isInitialized,getClassLoader());

}catch(ClassNotFoundException e) {

LOGGER.error("load class failure",e);throw newRuntimeException(e);//e.printStackTrace();

}returncls;

}/*** 獲取指定包名下的所有類文件/文件夹和jar包

*@parampackageName

*@return

*/

public static Set>getClassSet(String packageName){

Set> classSet = new HashSet>();try{//获取资源的url枚举

Enumeration 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 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();

}returnclassSet;

}/*** 加载指定包下面的jar和class文件

*@paramclassSet 存class的集合

*@parampackagePath 包的真实路径 D:***

*@parampackageName 包名 *.*.**/

private static void addClass(Set>classSet,String packagePath,String packageName){//System.out.println(packageName);//System.out.println(packagePath);//获得包下面的所有文件(.class和文件夹)

File[] files = new File(packagePath).listFiles(newFileFilter() {//文件过滤器,只获取.class结尾的文件和文件将夹

public booleanaccept(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集合中

*@paramclassSet 存被加载类的集合

*@paramclassName 类的全名 packageName.className*/

private static void doAddClass(Set>classSet,String className){

Class> cls = loadClass(className,false);

classSet.add(cls);

}public static voidmain(String[] args) {//loadClass("org.smart4j.framework.util.Cls",false);//Cls cls = new Cls();//加載環境中的jar包中的報名

Set> classSet = getClassSet("org.smart4j.framework.util");

System.out.println(classSet);

}

}

注解

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

控制器注解代码

packageorg.smart4j.framework.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/*** 控制器注解*/@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)public @interfaceController {

}

Action方法注解代码

/*** 控制器注解

* Action方法注解*/@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)public @interfaceAction {/*** 请求类型与路径*/String value();

}

服务类注解代码

/*** 服务类注解*/@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)public @interfaceService {

}

依赖注入注解代码

/*** 依赖注入注解*/@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)public @interfaceInject {

}

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

/*** @program: ClassHelper

* @description: 类操作助手

* 获取所有Controller和Service类的集合

**/

public classClassHelper {/*** 定义类集合*/

private static final Set>CLASS_SET;static{

String basePackage=ConfigHelper.getAppBasePackage();

CLASS_SET=ClassUtil.getClassSet(basePackage);

}/*** 获取应用包名下的所有类

*@return

*/

public static Set>getClassSet(){returnCLASS_SET;

}/*** 获取所有Controller类

*@return

*/

public static Set>getControllerClassSet(){

Set> classSet = new HashSet>();for (Class>cls : CLASS_SET){if (cls.isAnnotationPresent(Controller.class)){

classSet.add(cls);

}

}returnclassSet;

}/*** 获取所有Service类

*@return

*/

public static Set>getServiceClassSet(){

Set> classSet = new HashSet>();for (Class>cls : CLASS_SET){if (cls.isAnnotationPresent(Service.class)){

classSet.add(cls);

}

}returnclassSet;

}/*** 获取应用包名下的所有bean类(Controller和Service)

*@return

*/

public static Set>getBeanClassSet(){

Set> beanClassSet = new HashSet>();

beanClassSet.addAll(getControllerClassSet());

beanClassSet.addAll(getServiceClassSet());returnbeanClassSet;

}

}

实现Bean容器

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

packageorg.smart4j.framework.util;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.lang.reflect.Field;importjava.lang.reflect.Method;/*** 反射工具

* 实现bean容器*/

public classReflectionUtil {private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);/*** 创建实例

*@paramcls 已经加载的类

*@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 newRuntimeException(e);

}returninstance;

}/*** 调用方法

*@paramobj 调用方法的实例对象

*@parammethod 方法

*@paramargs 方法参数

*@return

*/

public staticObject 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 newRuntimeException(e);

}returnresult;

}/*** 设置成员变量的值

*@paramobj 实例对象

*@paramfield 字段

*@paramvalue 字段值*/

public static voidsetField(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 newRuntimeException(e);

}

}

}

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

packageorg.smart4j.framework.helper;importorg.smart4j.framework.util.ReflectionUtil;importjava.util.HashMap;importjava.util.Map;importjava.util.Set;/*** Bean助手类

**/

public classBeanHelper {/*** 定义bean映射(用于存放Bean类与Bean实例的映射关系)*/

private static final Map,Object> BEAN_MAP = new HashMap, Object>();static{//获取所有Controller和Service

Set> beanClassSet =ClassHelper.getBeanClassSet();//遍历所有的Controlller和Service类

for (Class>beanClass:beanClassSet){

Object obj= ReflectionUtil.newInstance(beanClass); //将类实例化

BEAN_MAP.put(beanClass,obj); //将类和实例放入Map中 Map

}

}/*** 获取Bean映射

*@return

*/

public static Map, Object>getBeanMap() {returnBEAN_MAP;

}/*** 根据Class获取bean实例

*@paramcls bean实例所属的类

*@param 类的实例对象

*@return

*/

public static T getBean(Classcls){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>,Object结构,记录了类与对象的映射关系)。然后遍历这个映射关系,分别取出Bean类与Bean实例,进而通过反射获取类中所有的成员变量。继续遍历这些成员变量,在循环中判断当前成员变量是否带有Inject注解,若带有该注解,则从Bean Map 中根据Bean实例。最后通过ReflectionUtil#setField方法来修改当前成员变量的值。

packageorg.smart4j.framework.helper;importorg.smart4j.framework.ArrayUtil;importorg.smart4j.framework.annotation.Inject;importorg.smart4j.framework.util.ReflectionUtil;importjava.lang.reflect.Field;importjava.util.Map;/*** 依赖注入助手类*/

public classIocHelper {static{//获取所有bean类与Bean实例类之间的关系的集合(简称BeanMap)

Map,Object> beanMap =BeanHelper.getBeanMap();if(CollectionUtil.isNotEmpty(beanMap)){//遍历beanMap

for (Map.Entry,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类,代码如下

packageorg.smart4j.framework;importorg.apache.commons.lang3.ArrayUtils;/*** 数组工具类*/

public classArrayUtil {/*** 判断数组是否为空

*@paramarray

*@return

*/

public static booleanisNotEmpty(Object[] array){return !isEmpty(array);

}/*** 判断数组是否非空

*@paramarray

*@return

*/

public static booleanisEmpty(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类

packageorg.smart4j.framework.bean;importorg.apache.commons.lang3.builder.EqualsBuilder;importorg.apache.commons.lang3.builder.HashCodeBuilder;/*** 封装请求信息*/

public classRequest {/*** 请求方法*/

privateString requestMethod;/*** 请求路径*/

privateString requestPath;publicRequest(String requestMethod,String requestPath){this.requestMethod =requestMethod;this.requestPath =requestPath;

}publicString getRequestMethod() {returnrequestMethod;

}public voidsetRequestMethod(String requestMethod) {this.requestMethod =requestMethod;

}publicString getRequestPath() {returnrequestPath;

}public voidsetRequestPath(String requestPath) {this.requestPath =requestPath;

}

@Overridepublic inthashCode() {return HashCodeBuilder.reflectionHashCode(this);

}

@Overridepublic booleanequals(Object obj) {return EqualsBuilder.reflectionEquals(this,obj);

}

}

Handler类

packageorg.smart4j.framework.bean;importjava.lang.reflect.Method;/*** @program: Handler

* @description: 封装Action信息

**/

public classHandler {/*** Controller类*/

private Class>controllerClass;/*** Action方法*/

privateMethod actionMethod;public Handler(Class>controllerClass, Method actionMethod) {this.controllerClass =controllerClass;this.actionMethod =actionMethod;

}public Class>getControllerClass() {returncontrollerClass;

}publicMethod getActionMethod() {returnactionMethod;

}

}

ControllerHelper类

packageorg.smart4j.framework.helper;importorg.smart4j.framework.ArrayUtil;importorg.smart4j.framework.annotation.Action;importorg.smart4j.framework.bean.Handler;importorg.smart4j.framework.bean.Request;importjava.lang.reflect.Method;importjava.util.HashMap;importjava.util.Map;importjava.util.Set;/*** @program: ControllerHelper

* @description: 控制器助手类

*@author: QiuYu

* @create: 2018-09-27 15:34

**/

public final classControllerHelper {/*** 用于存放请求与处理器的映射关系(简称Action Map)*/

private static final Map ACTION_MAP = new HashMap();static{//获取所有Controller类

Set> 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= newRequest(requestMethod,requestPath);

Handler handler= newHandler(controllerClass,method);//初始化Action Map

ACTION_MAP.put(request,handler);

}

}

}

}

}

}

}

}/*** 获取Handler

*@paramrequestMethod

*@paramrequestPath

*@return

*/

public staticHandler getHandler(String requestMethod,String requestPath){

Request request= newRequest(requestMethod,requestPath);returnACTION_MAP.get(request);

}

}

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

初始化框架

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

packageorg.smart4j.framework;importorg.smart4j.framework.helper.BeanHelper;importorg.smart4j.framework.helper.ClassHelper;importorg.smart4j.framework.helper.ControllerHelper;importorg.smart4j.framework.helper.IocHelper;importorg.smart4j.framework.util.ClassUtil;/*** @program: HelperLoader

* @description: 加载相应的Helper类

**/

public classHelperLoader {public static voidinit(){

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参数类代码:

packageorg.smart4j.framework.bean;importorg.smart4j.framework.CastUtil;importjava.util.Map;/*** @program: Param

* @description: 请求参数对象

*@author: Created by Autumn

* @create: 2018-10-24 10:47*/

public classParam {private MapparamMap;public Param(MapparamMap) {this.paramMap =paramMap;

}/*** 根据参数名获取long型参数值

*@paramname

*@return

*/

public longgetLong(String name){returnCastUtil.castLong(paramMap.get(name));

}/*** 获取所有字段信息

*@return

*/

public MapgetMap(){returnparamMap;

}

}

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

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

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

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

View类

packageorg.smart4j.framework.bean;importjava.util.Map;/*** @program: View

* @description: 视图对象*/

public classView {/*** 视图路径*/

privateString path;/*** 模型数据*/

private Mapmodel = new HashMap();public View(String path, Mapmodel) {this.path =path;this.model =model;

}publicView addModel(String key,Object value){

model.put(key,value);return this;

}publicString getPath() {returnpath;

}public MapgetModel() {returnmodel;

}

}

Data类

packageorg.smart4j.framework.bean;/*** @program: Data

* @description: 返回数据对象*/

public classData {/*** 模型数据*/

privateObject model;publicData(Object model) {this.model =model;

}publicObject getModel() {returnmodel;

}

}

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

MVC框架中核心的DispatcherServlet类

packageorg.smart4j.framework;importorg.smart4j.framework.bean.Data;importorg.smart4j.framework.bean.Handler;importorg.smart4j.framework.bean.Param;importorg.smart4j.framework.bean.View;importorg.smart4j.framework.helper.BeanHelper;importorg.smart4j.framework.helper.ConfigHelper;importorg.smart4j.framework.helper.ControllerHelper;importorg.smart4j.framework.util.CodecUtil;importorg.smart4j.framework.util.JsonUtil;importorg.smart4j.framework.util.ReflectionUtil;importorg.smart4j.framework.util.StreamUtil;import javax.servlet.*;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.io.PrintWriter;importjava.lang.reflect.Method;importjava.util.Enumeration;importjava.util.HashMap;importjava.util.Map;/*** @program: DispatcherServlet

* @description: 请求转发器

*@author: Created by Autumn

* @create: 2018-10-24 11:34*/@WebServlet(urlPatterns= "/*",loadOnStartup = 0)public class DispatcherServlet extendsHttpServlet {

@Overridepublic void init(ServletConfig servletConfig) throwsServletException {//初始化相关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()+"*");

}

@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throwsServletException, 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 paramMap = new HashMap();

Enumeration 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= newParam(paramMap);//调用Action方法

Method actionMethod =handler.getActionMethod();

Object result=ReflectionUtil.invokeMethod(controllerBean,actionMethod,param);//处理Action方法返回值

if (result instanceofView){//返回JSP页面

View view =(View) result;

String path=view.getPath();if(StringUtil.isNotEmpty(path)){if (path.startsWith("/")){ //这里应该是判断是否有数据

resp.sendRedirect(req.getContextPath()+path);

}else{

Map model =view.getModel();for (Map.Entryentry:model.entrySet()){

req.setAttribute(entry.getKey(),entry.getValue());

}

req.getRequestDispatcher(ConfigHelper.getAppJspPath()+path).forward(req,resp);

}

}

}else if (result instanceofData){//返回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();

}

}

}

}

}

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

安装到Maven本地仓库

1.用Maven将项目打包成jar

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

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

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

此种为IDEA中安装包的代码

java -Dmaven.multiModuleProjectDirectory=F:\intellijIDEAworkspace\smart-framework -Dmaven.home=D:\Programmer_QY\apache-maven-3.5.0 -Dclassworlds.conf=D:\Programmer_QY\apache-maven-3.5.0\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

相当于以下两步

ecc88399406d1f2dc9c5bd115b9d8e5e.png

总结

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

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

结果截图

0098ee999614bae375629540bb1d363e.png

e2f022db55dd41b799d043586ddf3c50.png

746287ad4162c11ce314c911b8e6e9d8.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值