搭建轻量级Java Web 框架

本章学习

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

3.2 搭建开发环境

3.2.1 创建框架项目

创建一个 smart-framework 的项目

添加Maven 三坐标

<groupId>org.smarwn</groupId>

<artifactId>smart-framework</artifactId>

<version>1.0.0</version>

<dependencies>

再添加相关的依赖

<dependencies>

<!--Servlet-->

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>javax.servlet-api</artifactId>

<version>3.1.0</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>

<!-- slf4j日志文件管理包版本 -->

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-log4j12</artifactId>

<version>${slf4j.version}</version>

</dependency>

<!-- 导入Mysql数据库链接jar包 -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.38</version>

</dependency>

<!-- jackson json序列化工具 -->

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.3</version>

</dependency>

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.5.4</version>

</dependency>

<!--常用工具类-->

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-lang3</artifactId>

<version>3.3.2</version>

</dependency>

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-collections4</artifactId>

<version>4.0</version>

</dependency>

<!--apache Common DBUtils jar :JDBC的封装-->

<dependency>

<groupId>commons-dbutils</groupId>

<artifactId>commons-dbutils</artifactId>

<version>1.6</version>

</dependency>

<!-- 导入dbcp的jar包,用来在applicationContext.xml中配置数据库连接池 -->

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-dbcp2</artifactId>

<version>2.0.1</version>

</dependency>

</dependencies>

至此,smart-framework 的Maven 依赖配置结束

3.2.1 创建实例项目

除了smart-framework这个项目 我们还要再建一个使用该框架的项目,我们命名为 smartx

它只需要依赖 smart-framework 即可,详细pom.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>org.smarwn</groupId>

<artifactId>smartx</artifactId>

<version>1.0.0</version>

<dependencies>

<dependency>

<groupId>org.smartwn</groupId>

<artifactId>smart-framework</artifactId>

<version>1.0.0</version>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.apache.tomcat.maven</groupId>

<artifactId>tomcat7-maven-plugin</artifactId>

<version>2.2</version>

<configuration>

<path>/${project.artifactId}</path>

</configuration>

</plugin>

</plugins>

</build>

</project>

开发环境准备好后,那么我们就实现具体的细节。既然是一个框架,那么首先要考虑的问题就是配置,需要让配置尽可能的少,这样开发者的学习成本才会更低。

3.3定义框架配置项

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

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

smart.framework.jdbc.url=jdbc:mysql://127.0.0.1:3306/ssm

smart.framework.jdbc.username=root

smart.framework.jdbc.password=root

#定义初始连接数

smart.framework.jdbc.initialSize=0

#定义最大连接数

smart.framework.jdbc.maxActive=20

#定义最大空闲

smart.framework.jdbc.maxIdle=20

#定义最小空闲

smart.framework.jdbc.minIdle=1

#定义最长等待时间

smart.framework.jdbc.maxWait=60000

#基础包名

smart.framework.app.base_package=org.smartwn.smartx

#定义js文件路径

smart.framework.app.jsp=/WEB-INF/view/

#定义静态资源文件路径(js.css.图片)

smart.framework.app.asset_path=/asset/

 

3.4 加载配置项

上面的配置文件有了,那么我们如何根据配置项的名称来获取配置项的取值呢?这是框架需要做的事,因此,我们在smart-farmework 项目中创建一个名为ConfigHelper 的助手类,让它读取smart.properties 配置文件。

先定义接口

package org.smartwn.framework;

/**

* 提供相关配置的常量

* Created by Administrator on 2017/7/11.

*/

public interface ConfigConstant {

String CONFIG_FILE = "smart.properties";

String JDBC_DRIVERD ="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 JDBC_INITIALSIZE ="smart.framework.jdbc.initialSize";

//定义最大连接数

String JDBC_MAXACTIVE ="smart.framework.jdbc.maxActive";

//定义最大空闲

String JDBC_MAXIDLE ="smart.framework.jdbc.maxidle";

//定义最小空闲

String JDBC_MINIDLE ="smart.framework.jdbc.minidle";

//定义最长等待时间

String JDBC_MAXWAIT ="smart.framework.jdbc.maxwait";

//基础包名

String APP_BASE_PACKAGE ="smart.framework.app.base_package";

//定义js文件路径

String APP_JSP ="smart.framework.app.jsp";

//定义静态资源文件路径(js.css.图片)

String APP_ASSET_PATH ="smart.framework.app.asset_path";

}

 

在定义助手类

package org.smartwn.framework.helper;

 

import org.smartwn.framework.ConfigConstant;

import org.smartwn.framework.util.PropsUtil;

 

import java.util.Properties;

 

/**

* 属性文件助手类

* Created by Administrator on 2017/7/11.

*/

public final 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_DRIVERD);

}

/**

* 获取JDBC 路劲

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

}

/**

* 获取JDBC 初始连接数

* @return

*/

public static String getJDBCInitialsize(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_INITIALSIZE);

}

/**

* 获取JDBC 最大连接数

* @return

*/

public static String getJDBCMaxactive(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_MAXACTIVE);

}

/**

* 获取最大空闲

* @return

*/

public static String getJDBCMaxidle(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_MAXIDLE);

}

/**

* 获取最小空闲

* @return

*/

public static String getJDBCMinidle(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_MINIDLE);

}

/**

* 获取最长等待时间

* @return

*/

public static String getJDBCMaxwait(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_MAXWAIT);

}

/**

* 获取应用基础包名

* @return

*/

public static String getAppBasePackage(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_BASE_PACKAGE);

}

/**

* 获取应用jsp路径

* @return

*/

public static String getAppJsp(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_JSP,"/WEB_INF/view/");

}

/**

* 获取静态资源路径

* @return

*/

public static String getAppAssetPath(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_ASSET_PATH,"/asset/");

}

}

3.5 开发一个类加载器

我们需要开发一个“类加载器”来加载所有该基础包下的所有类,比如使用了某注解的类或者实现了某接口的类,再或者继承了某父类的所有子类等

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

package org.smartwn.framework.util;

import org.slf4j.LoggerFactory;

import org.slf4j.Logger;

import java.io.File;

import java.io.FileFilter;

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;

 

/**

* 加载类

* Created by Administrator on 2017/7/12.

*/

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:"+className,e);

throw new RuntimeException();

}

return cls;

}

/**

* 获取指定包名下的所有类

*/

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().replace("%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(".")).replace("/",".");

doAddClass(classSet,className);

}

}

}

}

}

}

 

}

}catch(Exception e){

LOGGER.error("get class Set failure:"+packageName,e);

throw new RuntimeException();

}

return classSet;

}

public 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(subPackagePath)){

subPackagePath = packagePath+"/"+subPackagePath;

}

String subPackageName = filename;

if (StringUtil.isNotEmpty(packageName)){

subPackageName= packageName+"."+subPackageName;

}

doAddClass(classSet,subPackageName);

}

}

}

public static void doAddClass(Set<Class<?>> classSet,String className){

Class<?> cls = loadClass(className,false);

classSet.add(cls);

}

}

我们的目标是在控制类上使用Controlle、Service、action、inject注解,所以我们要定义4个注解类

控制器注解代码:

package org.smartwn.framework.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 控制器注解

* Created by Administrator on 2017/7/13.

*/

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface Controller {

}

Action注解代码:

package org.smartwn.framework.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 方法注解

* Created by Administrator on 2017/7/13.

*/

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Action {

/**

* 请求类型路劲

*/

String value();

}

服务类注解代码:

package org.smartwn.framework.annotation;

 

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 服务类注解

* Created by Administrator on 2017/7/13.

*/

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Service {

}

依赖注入类注解代码:

package org.smartwn.framework.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 依赖注入注解

* Created by Administrator on 2017/7/13.

*/

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Inject {

}

由于我们在smart.properties中定义了base_package,通过classUtil 来记载她,所以有必要提供一个ClassHelper助手类,让他分别获取应用包下的所有类,所有service类、所有controller类,此外我们将带有Controller和Service的注解的类所产生的对象,理解为Smart框架锁管理的Bean,所以有必要再ClassHelper 类中增加一个获取应用包名下所有Bean类的方法,细节代码如下:

package org.smartwn.framework.helper;

 

import org.smartwn.framework.ConfigConstant;

import org.smartwn.framework.annotation.Controller;

import org.smartwn.framework.annotation.Service;

import org.smartwn.framework.util.ClassUtil;

 

import java.util.HashSet;

import java.util.Set;

 

/**

* 类操作助手类

* Created by Administrator on 2017/7/13.

*/

public final 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;

}

/**

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

*/

public static Set<Class<?>> getServiceClassSet(){

Set<Class<?>> classSet = new HashSet<Class<?>>();

for (Class<?> cls:CLASS_SET){

if(cls.isAnnotationPresent(Service.class)){//如果指定类型的注释存在于此元素上,则返回 true

classSet.add(cls);

}

}

return classSet;

}

/**

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

*/

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;

}

/**

* 获取应用包名下的所有Bean类(包括Service 、Controller)

*/

public static Set<Class<?>> getBeanClassSet(){

Set<Class<?>> classSet = new HashSet<Class<?>>();

classSet.addAll(getServiceClassSet());

classSet.addAll(getControllerClassSet());

return classSet;

}

}

像这样我们使用ClassHelper 封装了ClassUtil类,并提供了一系列的助手方法

3.6 实现Bean容器

使用ClassHelper类可以获取所有加载的类,但无法通过类实例化对象,因此,需要提供一个反射工具类, 我们不妨将该类命名为ReflectionUtil,代码如下:

package org.smartwn.framework.util;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

/**

* 反射工具类

* Created by Administrator on 2017/7/14.

*/

public final class ReflectionUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);

/**

* 创建实例

* @param cls

* @return

*/

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;

}

/**

* 调用方法

* @param obj

* @param method

* @param args

* @return

*/

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("invaoke Method failure",e);

throw new RuntimeException(e);

 

}

return result;

}

/**

* 设置成员变量的值

* @param obj

* @param field

* @param args

*/

public static void setField(Object obj, Field field,Object...args){

try {

field.setAccessible(true);

field.set(obj,args);//将args赋值给obj的变量

} catch (Exception e) {

LOGGER.error("invaoke Field failure",e);

throw new RuntimeException(e);

}

}

}

我们需要获取所有被Smart框架管理的类,此时需要调用ClassHelper 类的getBeanClassSet方法 随后需要循环调用ReflectionUtil类的newInstance方法,根据类来实例化对象,最后放到Map中,so 我们还要建一个BeanHelper,代码如下:

package org.smartwn.framework.helper;

import org.smartwn.framework.util.ReflectionUtil;

import java.util.HashMap;

import java.util.Map;

import java.util.Set;

/**

* Created by Administrator on 2017/7/14.

*/

public class BeanHelper {

private static final Map<Class<?>,Object> BEAN_MAP = new HashMap<Class<?>, Object>();

/**

* 用于Bean映射 (存放Bean类与Bean是实例映射关系)

*/

static{

Set<Class<?>> classSet = ClassHelper.getBeanClassSet();

for(Class<?> cls:classSet){

Object obj = ReflectionUtil.newInstance(cls);

BEAN_MAP.put(cls, obj);

}

}

/**

* 获取Bean映射

* @return

*/

public static Map<Class<?>,Object> getBeanMap(){

return BEAN_MAP;

}

/**

* 获取Bean实例

* @param cls

* @param <T>

* @return

*/

@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);

}

}

现在 BeanHelper就相当于一个Bean 容器了,so我们只需要调用GetBean方法 传入一个Bean类就可以获取实例。

 

3.7 实现依赖注入功能

现在我们实现实例化 Controller/Service 的实例 那么 我们如何去实例化它们里面成员变量呢,还记得之前@Inject么,那么谁来实例化它们呢,不是用开发者用new来实例,而是要框架自身去实例化它们,想这样的类实例化过程,我们称她为IOC(invoersion of Control),控制不是有开发者决定,而是反转给框架了,一般我们也将控制反转成为DI(Dependency Injection,依赖注入),那么如何实现依赖注入?

最简单的办法是:先通过BeanHelper 获取所有BeanMap(一个map<Class<?>,Object>结构,记录类与对象的映射关系),然后遍历这个映射关系,分别取出Bean类和Bean实例,进而通过反射获取类中所有的成员变量。继续遍历这些成员变量,在循环中判断当前成员变量是否带有Inject注解,若带有着给他赋值,代码如下:

package org.smartwn.framework.helper;

import org.smartwn.framework.annotation.Inject;

import org.smartwn.framework.util.ArrayUtil;

import org.smartwn.framework.util.CollectionUtil;

import org.smartwn.framework.util.ReflectionUtil;

import java.lang.reflect.Field;

import java.util.HashMap;

import java.util.Map;

/**

* 依赖注入助手类

* Created by Administrator on 2017/7/14.

*/

public final class IcoHelper {

static{

//获取所有的Bean类和Bean实例化之间的映射关系(简称Bean map)

Map<Class<?>,Object> beanMap = BeanHelper.getBeanMap();

if(CollectionUtil.isNotEmpty(beanMap));

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.getFields();

if(ArrayUtil.isNotEmpty(beanFields)){

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框架中所管理的对象都是单例的,由于IOC框架还是从BeanHelper中获取Bean Map的,儿Bean Map 中对象都是事先创建好并放入这个Bean容器的,所以所有对象都是单例。

3.8 加载Controller

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

说先我们要定义一个Request类,代码如下:

package org.smartwn.framework.bean;

import org.apache.commons.lang3.builder.EqualsBuilder;

import org.apache.commons.lang3.builder.HashCodeBuilder;

/**

* 封装请求信息

* Created by Administrator on 2017/7/17.

*/

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 String getRequestPath() {

return requestPath;

}

public int hashCode(){

return HashCodeBuilder.reflectionHashCode(this);

}

public boolean equal(Object obj){

return EqualsBuilder.reflectionEquals(this,obj);

}

}

然后再编写一个Handler类 代码如下:

package org.smartwn.framework.bean;

import java.lang.reflect.Method;

/**

* 封装Action类

* Created by Administrator on 2017/7/17.

*/

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.smartwn.framework.helper;

import org.smartwn.framework.annotation.Action;

import org.smartwn.framework.bean.Handler;

import org.smartwn.framework.bean.Request;

import org.smartwn.framework.util.ArrayUtil;

import org.smartwn.framework.util.CollectionUtil;

import java.lang.reflect.Method;

import java.util.HashMap;

import java.util.Map;

import java.util.Set;

/**

* 控制器助手类

* Created by Administrator on 2017/7/17.

*/

public final class ControllerHelper {

/**

* 用于存放请求与处理器的映射关系

*/

private static Map<Request,Handler> ACTION_MAP =new HashMap<Request, Handler>();

/**

* 获取所有的Controller 类

*/

static{

Set<Class<?>> controllerCalssSet = ClassHelper.getControllerClassSet();

if(CollectionUtil.isNotEmpty(controllerCalssSet)){

for(Class<?> controllerClass:controllerCalssSet){

//获取Controller中方法定义

Method[] methods = controllerClass.getMethods();

if(ArrayUtil.isNotEmpty(methods)){

//遍历Cpntroller的方法

for(Method method:methods){

//判断是否有Action的注解

if(method.isAnnotationPresent(Action.class)){

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.put(request,handler);

}

}

};

}

}

}

}

}

/**

* 获取 Handler

*/

public static Handler getHandler(String requestMethod,String requestPath){

Request request = new Request(requestMethod,requestPath);

return ACTION_MAP.get(request);

}

}

可见ControllerHelper 中封装了一个Action Map通过他来存放Request 和 Handler 之间的映射关系,然后通过ClassHelper 获取所有带Controller注解的参数,然后遍历获取Action 注解中的URL,最后初始化request和heandler

 

3.9 初始化框架

用过上面的过程,我们创建了ClassHelper、BeanHelper、ControllerHelper、IocHelper,这4个Helper类需要通过一个入口程序来加载他们,实际上是加载他们的静态块,所以我们要写一个HelperLoader类来加载他们,代码如下:

package org.smartwn.framework;

import org.smartwn.framework.helper.BeanHelper;

import org.smartwn.framework.helper.ClassHelper;

import org.smartwn.framework.helper.ControllerHelper;

import org.smartwn.framework.helper.IocHelper;

import org.smartwn.framework.util.ClassUtil;

/**

* 加载相应的Helper类

* Created by Administrator on 2017/7/22.

*/

public final 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);

}

}

}

现在我们就可以直接调用HelperLoader 的init方法来初始化Helper类了.实际上,当我们第一次访问类时,就会加载static块,这里只是为了让加载更加集中。

3.10 请求转发器

以上都是为了这一步做准备,我们需要编写一个servlet,让他来处理所有的请求, 从HttpServletRequest对象中获取请求方法和请求路径,通过ControllerHelper#getHandler方法来获取Handler对象。

当拿到Handler对象后,我们就可以获取Controller的类,进而通过BeanHelper.getBean方法获取Controller的实例对象,随后可以从HttpServletRequest对象中获取所有请求参数,并将器初始哈到一个名为Param的对象中,Param类的代码如下:

package org.smartwn.framework.bean;

import org.smartwn.framework.util.CastUtil;

import java.util.Map;

/**

* 请求参数对象

* Created by Administrator on 2017/7/22.

*/

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类中,会有一系列的get方法,可以通过参数名获取指定类型的参数值,也可以获取所有参数的Map结构,

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

1、若返回的View类型的视图对象,则返回jsp页面。

2、若返回的Data类型的数据对象,则返回一个JSON数据。

我们需要根据以上两种情况来判断Action的返回值,并做到不同的处理。

首先,看看View,代码如下:

package org.smartwn.framework.bean;

import java.util.HashMap;

import java.util.Map;

/**

* 返回视图对象

* Created by Administrator on 2017/7/22.

*/

public class View {

/**

* 视图对象

*/

private String path;

/**

* 模型数据

*/

private Map<String,Object> model;

public View(String path){

this.path=path;

model =new HashMap<String, Object>();

}

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;

}

}

由于视图中可包含模型数据,因此View中包括了视图路径和该视图中所需的模型数据Map<String,Object>,然后看看Data类,代码如下:

package org.smartwn.framework.bean;

/**

* 返回数据对象

* Created by Administrator on 2017/7/22.

*/

public class Data {

/**

* 模型数据

*/

private Object model;

public Object getModel() {

return model;

}

public void setModel(Object model) {

this.model = model;

}

}

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

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

package org.smartwn.framework;

import com.alibaba.fastjson.JSON;

import org.smartwn.framework.bean.Data;

import org.smartwn.framework.bean.Handler;

import org.smartwn.framework.bean.Param;

import org.smartwn.framework.bean.View;

import org.smartwn.framework.helper.BeanHelper;

import org.smartwn.framework.helper.ConfigHelper;

import org.smartwn.framework.helper.ControllerHelper;

import org.smartwn.framework.util.*;

import javax.servlet.ServletConfig;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.ServletRegistration;

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;

/**

* 请求转发器

* Created by Administrator on 2017/7/22.

*/

@WebServlet(urlPatterns="/*",loadOnStartup = 0)

public class DispatcherServlet extends HttpServlet{

@Override

public void init(ServletConfig config) throws ServletException {

//初始化相关的Helper类

HelperLoader.init();

//获取ServletContext对象

ServletContext servletConfig = config.getServletContext();

//注册处理jsp的 Servlet

ServletRegistration jspServlet =servletConfig.getServletRegistration("jsp");

jspServlet.addMapping(ConfigHelper.getAppJsp()+"*");

//注册处理静态资源默认Servlet

ServletRegistration defaultServlet =servletConfig.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类和其实例

Class<?> controllerCalss = handler.getControllerClass();

Object controllerBean = BeanHelper.getBean(controllerCalss);

//创建请求参数对象

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

}

String body= CodecUtil.decodeURL(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){

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(path).forward(req,resp);

}

}

}else if(result instanceof Data){

Data data = (Data)result;

Object model = data.getModel();

if(model != null){

resp.setContentType("application/json");

resp.setCharacterEncoding("UTF-8");

PrintWriter out = resp.getWriter();

String json = JsonUtil.toJson(model);

out.write(json);

out.flush();

out.close();

}

}

}

}

}

在dispatcherServlet 中用了几个新的工具类:

Stream类代码如下:

package org.smartwn.framework.util;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import javax.servlet.ServletInputStream;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

/**

* 流操作工具类

* Created by Administrator on 2017/7/23.

*/

public final class StreamUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(StreamUtil.class);

//从输入流中获取字符串

public static String getString(ServletInputStream inputStream) {

StringBuilder sb = new StringBuilder();

try{

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

String line;

while((line =reader.readLine())!=null){

sb.append(line);

}

}catch (IOException e){

LOGGER.error("get String Stram failure",e);

throw new RuntimeException(e);

}

return sb.toString();

}

}

CodecUtil 类用于编码与解码操作,代码如下:

package org.smartwn.framework.util;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.net.URLDecoder;

import java.net.URLEncoder;

/**

* 编码与解码操作工具类

* Created by Administrator on 2017/7/23.

*/

public final class CodecUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(CodecUtil.class);

/**

* 将URL 编码

* @param source

* @return

*/

public static String encodeURL(String source){

String target;

try{

target = URLEncoder.encode(source,"UTF-8");

}catch (Exception e){

LOGGER.error("encode url failure",e);

throw new RuntimeException(e);

}

return target;

}

/**

* 将URl 解码

* @param source

* @return

*/

public static String decodeURL(String source){

String target;

try {

target = URLDecoder.decode(source, "UTF-8");

}catch (Exception e){

LOGGER.error("decode url failure",e);

throw new RuntimeException(e);

}

return target;

}

}

Json类用于处理JSON 与POJO之间转换,基础Jackson实现,代码如下:

package org.smartwn.framework.util;

import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

/**

* Created by Administrator on 2017/7/23.

*/

public final class JsonUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(CodecUtil.class);

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

/**

* 将POPJ转化为JSON

* @param obj

* @return

*/

public static <T> String toJson(T obj){

String json;

try{

json = OBJECT_MAPPER.writeValueAsString(obj);

}catch(JsonProcessingException e){

LOGGER.error("convert POJO to JSON failure",e);

throw new RuntimeException(e);

}

return json;

}

/**

* 将json 转为 Pojo

* @param json

* @param type

* @param <T>

* @return

*/

public static <T> T fromJson(String json,Class<T> type){

T pojo;

try{

pojo = OBJECT_MAPPER.readValue(json,type);

}catch (Exception e){

LOGGER.error("convert JSON to POJO failure",e);

throw new RuntimeException(e);

}

return pojo;

}

}

至此,一款简单的MVC框架就开发完毕了,通过这个DispatcherServlet来处理所有请求,根据请求信息从ControllerHelper中获取对应的Action方法,然后使用反射技术调用Action 方法,同时需要具体的传入

方法参数,最后拿到返回值判断并判断返回值的类型,进行相应的处理。

3.11 总结

通过Controller注解来定义Controller类,通过Inject注解来实现依赖注入,通过Action来注解定义Action方法

通过一系列的Helper类来初始化MVC框架,通过DispatcherServlet 来处理所有请求,根据请求方法与请求路径调用具体的Action的返回类型,若为View 则返回调到Jsp页面,若为Data类型则返回JSON数据。

整个框架基本能跑起来了,但里面还存在大量需要优化的地方,此外还有一些非常好的特性未提供,比如AOP,

我们可以使用这个特性来实现横向拦截操作,比如性能分析,比如日志收集,安全控制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值