WebFramework的作用
WebFramework简称Web框架,比较知名的比如Spring,其主要是通过技术手段管理模块中的类的生命周期,更加有效的管理内存.
如果想要加深框架的理解,建议手动编写黄勇先生书的1-3章节,由于黄勇先生并未对具体功能做细致解释,所以这篇文章仅仅是补充作用,而且编写时一定要用JDK1.6和Tomcat7,我用JDK1.8和Jetty9代码没有跑起来…..
由于Web开发经验有限,所以整篇文章理解可能会存在谬误的地方,如果碰到,你跳进屏幕来打我呀 ㄟ<(=▔.▔=)>ㄏ
WebFramework的技术点
- JavaBean: 普通的Class,作为Model,成员变量有Get/Set方法,具有一个无参数的构造函数
- 反射Reflection: 一种运行时机制,本质是通过通过类名(字符串)来实例化/传参/执行函数
- 反转控制(Inversion of Control):一种去耦合技术,通过反射技术管理Class的生命周期
- 依赖注入(Dependency Injection):一种链接技术,用来对成员变量赋值(实质是链接因为IOC带来的耦合丢失)
- 注解(Annotation): 一种标记技术,类似于接口,框架通过这种标记技术来管理不同注解的类/变量/方法的声明周期
SmartFramework架构图
除了Spring这种常用的框架外,阿里巴巴黄勇的书提出了一个观点,应该首先了解框架怎么运作,再去学习框架,从而获得更加清醒的认识.本人由于是初学Java,所以先根据其书1-3章,重现了其轻量级框架SmartFramework,并且进行了一定的总结.
首先进行一些准备工作
SmartFramework工具类
整个框架除了主流程之外还使用到了以下工具类
常用工具中包括:字符串转换/容器是否为空判断/Json编码解码/URL编码解码/数据流编码解码/配置文件读取
SmartFramework注解
注解的详细介绍见另一篇文章,SmartFramework四个注解
- Action: 注解网络层用于接收HTTP的Request和Post请求的Handler函数
- Controller: 注解网络层用于要对Request的进行处理的Class类
- Service: 注解Java层用于逻辑处理Object的Class类,如和数据库息息相关的DAO类等
- Inject: 注解Java层Controller中需要的Service类,进行依赖注入
四个注解的ElementType分别是Action(Method),Controller(TYPE),Service(TYPE),Inject(Field).
SmartFrameworkBean类
Bean类共有五个,其中框架类的两个
- Request: 存储两个String类型成员变量RequestMethod/RequestPath
- Handler: 存储Class类型的ControllerClass,存储Method的类型的HandlerMethod
前端类3个
- Data: 一个Object对象,用于储存Json类等数据的Object
- View: 一个String类型的页面JSP地址Path,一个Map类型的Model,用于接收ParamMap
- Param: 储存一个Map类型的ParamMap,Key为String类型参数名,Value为Object类型的参数值
SmartFramework工作流程
以下是工作流程图
静态启动(Static Init)
- 首先把整个框架的入口放在HttpServlet
- 重写Init函数,以便于HttpServlet启动进行执行
- 使用类加载工具把所有的HelperClass进行顺序类加载
以下是HttpServlet代码示例
@Override
public void init(ServletConfig servletConfig) throws ServletException {
HelperLoader.init();
.....
}
通过HelperLoader进行循环加载
public static void init() {
Class<?>[] classList = {
ClassHelper.class,
BeanHelper.class,
InjectHelper.class,
ControllerHelper.class
};
for (Class<?> cls: classList) {
ClassUtil.loadClass(cls.getName(),false);
}
}
所有的Helper变量都拥有两个部分
- 静态变量容器: 一个静态变量用于存放静态代码获取到的Object
- 静态代码: 保证在类加载后静态代码被马上执行,并且把数据存放到静态成员变量
类加载(Class Load)
- 首选获取到类加载线程Tread
- 遍历文件夹/Jar包/.class文件取出加载了框架的所有Class
- 使用类加载工具把所有Class放入ClassSet
- 通过Tread加载Set中所有的类
ClassHelper静态变量和静态区代码
private static final Set<Class<?>> CLASS_SET;
static {
String basePackage = ConfigHelper.getAppBasePackage();
CLASS_SET = ClassUtil.getClassSet(basePackage);
}
反转控制(Bean IOC)
- 通过BeanHelper获取到所有的BeanClassName,准备一个BeanMap备用
- 然后利用反射工具ReflectionUtil对所有的Bean进行实例化
- 实例化包含Controller注解的Bean
- 实例化包含Service注解的Bean
- 以Class作为Key值,实例的Object作为Value放入BeanMap
BeanHelper静态变量和静态代码
private static final Map<Class<?>, Object> BEAN_MAP = new HashMap<Class<?>, Object>();
static {
Set<Class<?>> beanClassSet = ClassHelper.getBeanClassSet();
for (Class<?> beanClass : beanClassSet) {
Object obj = ReflectionUtil.newInstance(beanClass);
BEAN_MAP.put(beanClass,obj);
}
}
依赖注入(Inject)
黄勇先生在书中将依赖注入放入了Iochelper.java,个人认为是黄勇先生在一开始就混淆了依赖注入和控制反转的概念,所以我将该文件称为InjectHelper
- 遍历BeanMap的取出Key和Value
- 对每一个Key进行class.getDeclaredFields()取出所有Field
- 检查Field是不是含有Inject注解
- 存在Inject注解的通过Field.getType()得到FieldClass
- 以FieldClass作为Key去BeanMap查找,看是否存在已经被IOC实例化的对象
- 存在实例通过反射工具ReflectionUtil进行依赖注入
InjectHelper的静态区代码
public final class InjectHelper {
static {
//Get All Bean Class And Bean Instance
Map<Class<?>, Object> beanMap = BeanHelper.getBeanMap();
if(CollectionUtil.isNotEmpty(beanMap)) {
//一级遍历Bean Map
for (Map.Entry<Class<?>,Object>beanEntry : beanMap.entrySet()) {
//获取Bean类和实例
Class<?> beanClass = beanEntry.getKey();
Object beanInstance = beanEntry.getValue();
//获取一级遍历中每个Bean类所定义的成员变量Bean Field
Field[] beanFields = beanClass.getDeclaredFields();
if (ArrayUtil.isNotEmpty(beanFields)) {
//对Bean Fields进行二级遍历
for (Field beanField : beanFields) {
//检查存在Inject注解的Field(备注1)
if (beanField.isAnnotationPresent(Inject.class)) {
Class<?> beanFieldClass = beanField.getType();
Object beanFieldInstance = beanMap.get(beanFieldClass);
//BeanMap中存在已经被IOC实例化的对象
if (beanFieldInstance !=null) {
//进行依赖注入,重新链接被IOC切断的耦合
ReflectionUtil.setField(beanInstance,beanField,beanFieldInstance);
}
}
}
}
}
}
}
}
依赖注入中最关键的是备注1,其配合IOC中第三第四步来共同使用
- IOC中进行实例化的有Controller注解的类,也有Service注解的类
- Service注解的类一般是作为Controller注解的类的成员变量,并且打上了Inject注解
- 由于JavaBean的无参数构造特性,Controller中的Service成员变量此时指向null
- 而BeanMap中又存在Service类的实例
- 依赖注入就是通过Field.set(ControllerObject, ServiceObject)进行注入
- 将Controller中的Service成员变量的指针指向BeanMap中的ServiceObject
由于控制反转(IOC)经常和依赖注入(DI)经常配合使用,所以好多将两者混为一谈,其实并不是这样.IOC是一种去耦合手段,而DI则是从新链接耦合的手段
请求映射(RequestMap)
按照思维逻辑每一个请求(Request)都是调用一个服务端功能,而去接收并处理这个请求的就是一个接收的方法(Handler),这一步就是将前端被标有Action注解的方法(Method)对应到每个请求的URL地址上.
- 使用一个静态变量ActionMap保存映射关系,Request为Key,Handler为Value
- 遍历带有Controller注解的Bean,并且遍历其中带有Action注解的Method方法
- 分析Action中的value方法的字符串,取出RequestMethod和RequestURL
- 使用RequestMthod和RequestURL实例化Request对象
- 使用Controller的Class和其Method实例化Handler对象
- 把对应的Request和Handler放入ActionMap
ControllerHelper的静态代码
public class ControllerHelper {
private static final Map<Request,Handler> ACTION_MAP = new HashMap<Request, Handler>();
static {
//获取所有的ControllerClass
Set<Class<?>> controllerClassSet = ClassHelper.getControllerClassSet();
if (CollectionUtil.isNotEmpty(controllerClassSet)) {
//一级遍历ControllerClass
for(Class<?> controllerClass : controllerClassSet) {
//取出一级遍历中的所有Method
Method[] methods = controllerClass.getMethods();
if (ArrayUtil.isNotEmpty(methods)) {
//二级遍历Method
for (Method method : methods) {
//查询带有Action注解的Method
if (method.isAnnotationPresent(Action.class)) {
Action action = method.getAnnotation(Action.class);
String mapping = action.value();
//分析Method中value的组成
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和Handler
Request request = new Request(requestMethod,requestPath);
Handler handler = new Handler(controllerClass,method);
//放入映射关系ActionMap
ACTION_MAP.put(request, handler);
}
}
}
}
}
}
}
}
}
请求响应(HTTP Response)
在SmartFramework框架搭建好之后,可以通过Jar包或者Maven的形式加入新的Project.在新的Project中使用需要遵循以下方案
- 使用Controller注解标记出要对请求响应的类
- 使用Service注解标记出后台实际处理请求的类
- 使用Inject注解标记出Controller中引入用来处理事务的Service类
- 使用Action标记出Controller中响应方法
- Action方法需要遵循使用了ParamBean的参数
- Action返回一个ViewBean或者DataBean(见SmartFrameworkBean讲解)
新ProjectController举例,Controller是Servlet的一个延伸,主要是处理HTTP请求中的逻辑关系
@Controller
public class TestController {
//此处对Service的Class进行注入,ServiceClass由IOC实例化
@Inject
private TestService testService;
//返回一个DataBean
@Action("get:/outputString")
public Data doTest(Param param) {
String string = testService.outputString();
return new Data(string);
}
//返回一个ViewBean
@Action("get:/index")
public View doIndex(Param param) {
return new View("index.jsp");
}
}
新ProjectService举例,Service是实际后台的逻辑,处理数据库操作等事务并作为一个Model被Controller引用
@Service
public class TestService {
public String outputString() {
String string = "Test Service Done";
return string;
}
}
框架的总结
根据文章Servlet的总结每一个HTTP请求都需要一个Servlet进行接收,这样的话会导致需要编写大量的Servlet让代码冗余不堪,而且每一个Servlet都需要一个实例占用内存导致资源浪费,而框架就可以解决这些问题.
- 框架将Servlet类作为入口,让Servlet作为转发器,分析请求并找到对应的Handler方法
- 框架通过反转控制和依赖注入保障了所有的Class在内存中只存在一个实例(单例模式),节省了内存
- 框架定义好了Bean,使用这些Bean规范化了参数和返回的Object
- 框架提供了一系列工具类
由于框架的这些处理,使得接口的开发更加清晰,而且类似于内存和生命周期的管理等复杂问题则不需要程序员考虑.
框架的AOP
框架还有一个重要的特性是AOP,面向切面的编程,由于是初步学习,准备等实际业务能力上来之后再来体会其中的奥妙
个人感想
以往在写前端的时候,自己思考过一种单例模式,万万没想到啊,后台框架这种反转控制和依赖注入把内存玩的这么溜,真是大开眼界.