一、框架简介
本框架是一个类似于Struts2+Spring的框架,目的在于个人钻研和技术分享,将流行技术框架Struts2、Spring中使用到的主要技术以较为简化的方式实现出来,给大家一个更直观的呈现。(注意:本框架本身不够完善,还不足以用于商用业务开发,代码可能存在缺陷,部分功能还有优化空间;同时要说明,Struts2、Spring的实际实现较为复杂(自然功能也强大),本框架借鉴中他们本来的一些实现方式,但也做了较大改动和简化)
二、涉及到的主要技术点
1.MVC三层分离(参考资料:Struts2与MVC基础入门)
1)M(Model)为业务处理逻辑处理及持久化操作等
2)V (View)为页面呈现,本架构中通过JSP呈现,Struts2中还可以通过Velocity、Freemarker来做展示。
3)C (Controller) 为action控制对各逻辑模块的调用和到视图层的跳转。
实现方式:
采用了类似struts2的配置文件格式,用于定义action、调用方法及跳转。
页面可以提交数据到Action中使用,Action中可以将数据写入Request或Session中,在JSP页面可以通过request.getAttribute或session.getAttribute方式获取到。
2.IOC容器(参考资料:IOC容器)
实现方式:采用了类似Spring的配置文件格式,用于定义各种bean及bean间依赖,支持单实例配置。
3.AOP (参考资料:Spring AOP 详解,java动态代理)
实现方式:采用了不同于Spring的aop配置,做了简化,可以用于定义切面和对应的切面操作类。说实话,AOP这些概念说起来非常晦涩难懂,简单点说,就是利用java里的动态代理机制,对指定的一些类中的一些方法进行拦截,在这些方法执行前后插入自定义的一些操作。如在所有action类中add方法前增加一个记录日志的操作,对于所有update方法记录执行所需要的时间。除了日志的记录,比较常用的还有事务。 不过始终铭记于心,这样的拦截对我们自己的代码都是有要求的,如你指定对add开头的方法进行拦截(方法关键字支持模糊匹配,*代表多个字符),那么自己所开发的所有代码在命名时必须要满足这个格式,如果命名为Add(a变成了大写的了),那就拦截不到了。当然,如果你把拦截的范围设置的大了,则有可能误伤,把一些本不应拦截的也给拦截了。
4.与Mybatis集成
实现方式:与mybatis 3.2.7进行集成,支持一个工厂类来产生SqlSessionFactory对象,用于DAO类中进行调用。
三、本框架与Struts2、Spring框架差异之处
1)没有支持Struts2中带有的拦截器功能
2)AOP实现方式与Spring的实现方式有差别,配置文件有较大变化
还有其他的差异,这里不一一赘述。
四、源代码下载:
https://github.com/jerrymousecn/miniMVC
其中mybatis_demo目录为与mybatis集成的样例,里面使用的数据库是mysql。
mybatis_demo\miniMVC_mybatis\mysql.sql文件为数据库表及数据创建语句;
如果需要使用此样例,需要检查ibatisConfiguration.xml中数据库配置是否正确(文件位于:mybatis_demo\miniMVC_mybatis\src\cn\jerry\mini_mvc\example\config\);
如果要自己编写其他的样例,则需要注意StudentMapper.xml、ibatisConfiguration.xml中各种路径配置是否正确(具体参考mybatis相关资料,本框架未对mybatis配置做出任何改变)
本文编写时,对于的代码发布版本:https://github.com/jerrymousecn/miniMVC/archive/1.7.zip
注:导入eclipse项目时要注意修改项目属性中"Java Build Path"对应的JRE路径,修改"Targeted Runtimes"对应的容器配置。
五、主要源代码:
1.Action控制类,入口过滤器
package cn.jerry.mini_mvc;
import java.io.File;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dom4j.DocumentException;
import cn.jerry.mini_mvc.aop.AopProxyFactory;
public class MiniMVCFilter implements Filter {
private ActionMappings actionMappings;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
ActionContext actionContext = new ActionContext(request,response);
ActionContext.setContext(actionContext);
try {
String shortUri = getShortURI(request);
if(isAcceptedAction(shortUri))
{
String actionName = getActionName(shortUri);
String redirectPagePath = actionMappings.execute(request.getParameterMap(), actionName);
RequestDispatcher dispatcher = request.getRequestDispatcher(redirectPagePath);
dispatcher.forward(request, response);
}
else
{
filterChain.doFilter(servletRequest, servletResponse);
}
}catch(Exception e)
{
e.printStackTrace();
}
finally {
ActionContext.clearUp();
}
}
private boolean isAcceptedAction(String shortURI)
{
if(shortURI.endsWith(Constants.DEFAULT_ACTION_SUFFIX))
return true;
return false;
}
private String getShortURI(HttpServletRequest request)
{
String totalURI = request.getRequestURI();
String contextPath = request.getContextPath();
String shortURI = totalURI.substring(contextPath.length());
return shortURI;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
ObjectFactory objectFactory =initObjectFactory(filterConfig);
initActionMapping(filterConfig,objectFactory);
}
private ObjectFactory initObjectFactory(FilterConfig filterConfig)
{
String beanConfigPath = filterConfig.getInitParameter("bean-config");
String beanConfigFullPath = getFullPath(filterConfig,beanConfigPath);
// objectFactory = BeanFactory.getInstance();
ObjectFactory objectFactory = AopProxyFactory.getInstance();
try {
objectFactory.init(beanConfigFullPath);
} catch (DocumentException e) {
e.printStackTrace();
}
return objectFactory;
}
private void initActionMapping(FilterConfig filterConfig,ObjectFactory objectFactory)
{
String configPath = filterConfig.getInitParameter("config");
String configFullPath = getFullPath(filterConfig,configPath);
actionMappings = ActionMappings.getInstance();
try {
actionMappings.init(configFullPath);
actionMappings.setObjectFactory(objectFactory);
} catch (DocumentException e) {
e.printStackTrace();
}
}
private String getFullPath(FilterConfig filterConfig,String relativePath)
{
String realPath = filterConfig.getServletContext().getRealPath("/");
return realPath +"WEB-INF"+File.separatorChar+"classes"+File.separatorChar+relativePath;
}
private String getActionName(String shortUri)
{
String actionName = shortUri.substring(1,shortUri.length()-Constants.DEFAULT_ACTION_SUFFIX.length());
return actionName;
}
@Override
public void destroy() {
}
}
2.对象工厂类,用于实现IOC容器,生成各种bean,支持单例模式
package cn.jerry.mini_mvc;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.dom4j.DocumentException;
import cn.jerry.mini_mvc.parser.XMLBean;
import cn.jerry.mini_mvc.parser.BeanParser;
import cn.jerry.mini_mvc.parser.XMLBeanProperty;
public class BeanFactory implements ObjectFactory{
private static BeanFactory beanFactory = new BeanFactory();
private Map
singletonMap = new HashMap
();
protected BeanFactory() {
}
public static BeanFactory getInstance() {
return beanFactory;
}
private Map
beanMap = new HashMap
();
@Override
public void init(String configFile) throws DocumentException
{
BeanParser beanParser = new BeanParser();
beanParser.init(configFile);
beanMap = beanParser.getBeanMap();
}
public XMLBean getBean(String beanName) throws Exception {
XMLBean bean = beanMap.get(beanName);
return bean;
}
@Override
public Object getInstance(Object obj) throws Exception {
return obj;
}
@Override
public Object getInstanceByBeanName(String beanName) throws Exception
{
if(singletonMap.get(beanName)!=null)
return singletonMap.get(beanName);
XMLBean bean = beanMap.get(beanName);
String className = bean.getClassName();
Object obj = Class.forName(className).newInstance();
injectObj(bean, obj);
if(bean.isSingleton())
{
saveSingletonBean(beanName, obj);
}
return obj;
}
public void injectObj(XMLBean bean,Object obj) throws Exception
{
if(bean.hasProperties())
{
Map
map = bean.getPropertyMap(); for(Entry
entry : map.entrySet()) { String propertyName = entry.getKey(); XMLBeanProperty beanProperty = entry.getValue(); Object beanInProperty; if(beanProperty.hasRefToOtherBean()) { beanInProperty = getInstanceByBeanName(beanProperty.getRefBeanName()); } else { beanInProperty = beanProperty.getValue(); } setBean(obj,propertyName,beanInProperty); } } } private void saveSingletonBean(String beanName,Object obj) { singletonMap.put(beanName, obj); } public void setBean(Object obj,String fieldName,Object fieldValue) throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException { BeanUtil.setBeanProperty(obj, fieldName, fieldValue); } }
3.AOP代理生成类(通过cglib方式生成)
package cn.jerry.mini_mvc.aop;
import java.lang.reflect.Method;
import java.util.Map;
import cn.jerry.mini_mvc.BeanUtil;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CgLibProxy extends BaseProxy implements MethodInterceptor {
public CgLibProxy(Map
aopAspectMap) {
super(aopAspectMap);
}
public Object getInstance(Object targetObj) {
setTargetObj(targetObj);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetObj.getClass());
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
BeanUtil.copyBeanProperties(targetObj, proxyObj);
return proxyObj;
}
public Object getInstance(Class targetClass) {
setTargetClass(targetClass);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
return proxyObj;
}
@Override
public Object intercept(Object proxyObj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
initAdvices(getTargetClass(), proxyObj, method, args);
execBeforeAdvice(getTargetClass(), proxyObj, method, args);
execAroundBeforeAdvice(getTargetClass(), proxyObj, method, args);
Object resultObj = proxy.invokeSuper(proxyObj, args);
execAroundAfterAdvice(getTargetClass(), proxyObj, method, args);
execAfterAdvice(getTargetClass(), proxyObj, method, args);
return resultObj;
}
}
package cn.jerry.mini_mvc.aop;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import cn.jerry.mini_mvc.BeanFactory;
public abstract class BaseProxy {
private Class targetClass;
private Object targetObj;
protected BeforeAdvice beforeAdvice;
protected AfterAdvice afterAdvice;
protected AroundAdvice aroundAdvice;
protected Map
aopAspectMap;
public BaseProxy(Map
aopAspectMap) {
this.aopAspectMap = aopAspectMap;
}
private void resetAdvices()
{
beforeAdvice = null;
afterAdvice = null;
aroundAdvice = null;
}
protected void initAdvices(Class targetClass, Object proxyObj,
Method method, Object[] args) throws Exception {
resetAdvices();
String classFullPath = targetClass.getCanonicalName();
String methodName = method.getName();
for (Entry
entry : aopAspectMap.entrySet()) {
AopAspect aopAspect = entry.getValue();
String classPattern = preparePattern(aopAspect.getClasses());
String methodPattern = preparePattern(aopAspect.getMethod());
if(isMatch(classFullPath, methodName, classPattern, methodPattern))
{
BeanFactory beanFactory = BeanFactory.getInstance();
beforeAdvice = (BeforeAdvice)beanFactory.getInstanceByBeanName(aopAspect.getBeforeAdvice());
afterAdvice = (AfterAdvice)beanFactory.getInstanceByBeanName(aopAspect.getAfterAdvice());
aroundAdvice = (AroundAdvice)beanFactory.getInstanceByBeanName(aopAspect.getAroundAdvice());
break;
}
}
}
public boolean isClassAccepted(Class clazz)
{
String classFullPath = clazz.getCanonicalName();
for (Entry
entry : aopAspectMap.entrySet()) { AopAspect aopAspect = entry.getValue(); String classPattern = preparePattern(aopAspect.getClasses()); if(isMatch(classFullPath, classPattern)) { return true; } } return false; } private boolean isMatch(String classFullPath, String methodName, String classPattern, String methodPattern) { if (isMatch(classFullPath, classPattern) && isMatch(methodName, methodPattern)) { return true; } else { return false; } } private boolean isMatch(String srcStr, String pattern) { Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(srcStr); if (m.find()) { return true; } else { return false; } } private String preparePattern(String inputPattern) { inputPattern = "^"+inputPattern+"$"; return inputPattern.replaceAll("\\*", ".*"); } public abstract Object getInstance(Class targetClass); public abstract Object getInstance(Object obj); protected void execBeforeAdvice(Class targetClass, Object proxyObj, Method method, Object[] args) { try { if (beforeAdvice != null) beforeAdvice.before(targetClass, proxyObj, method, args); } catch (Exception e) { e.printStackTrace(); } } protected boolean isToIntercept() { return false; } protected void execAfterAdvice(Class targetClass, Object proxyObj, Method method, Object[] args) { try { if (afterAdvice != null) afterAdvice.after(targetClass, proxyObj, method, args); } catch (Exception e) { e.printStackTrace(); } } protected void execAroundBeforeAdvice(Class targetClass, Object proxyObj, Method method, Object[] args) { try { if (aroundAdvice != null) aroundAdvice.before(targetClass, proxyObj, method, args); } catch (Exception e) { e.printStackTrace(); } } protected void execAroundAfterAdvice(Class targetClass, Object proxyObj, Method method, Object[] args) { try { if (aroundAdvice != null) aroundAdvice.after(targetClass, proxyObj, method, args); } catch (Exception e) { e.printStackTrace(); } } public void setBeforeAdvice(BeforeAdvice beforeAdvice) { this.beforeAdvice = beforeAdvice; } public void setAfterAdvice(AfterAdvice afterAdvice) { this.afterAdvice = afterAdvice; } public void setAroundAdvice(AroundAdvice aroundAdvice) { this.aroundAdvice = aroundAdvice; } protected Class getTargetClass() { return targetClass; } protected void setTargetClass(Class targetClass) { this.targetClass = targetClass; } protected Object getTargetObj() { return targetObj; } protected void setTargetObj(Object targetObj) { this.targetObj = targetObj; } }
截图:
1.输入框页面,输入姓名
2.结果页面,显示一个欢迎信息,其中的姓名来自前一个提交的页面
后台打印信息(大部分是AOP类打印的,用于展示AOP操作):
BeforeAdviceImpl1 targetObj: TestAction method: execute
AroundAdviceImpl1 targetObj: TestAction method: execute
test1 in TestDao ...
AroundAdviceImpl1 targetObj: TestAction method: execute
Time Elapsed: 1 ms
AfterAdviceImpl1 targetObj: TestAction method: execute