一、框架簡介
本框架是一個類似於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