本章学习
- 如何快速搭建自己的开发框架
- 如何加载并读取配置文件
- 如何实现一个简单的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,
我们可以使用这个特性来实现横向拦截操作,比如性能分析,比如日志收集,安全控制。