自己實現的簡單MVC框架(類似Struts2+Spring)

一、框架簡介

本框架是一個類似於Struts2+Spring的框架,目的在於個人鑽研和技術分享,將流行技術框架Struts2、Spring中使用到的主要技術以較爲簡化的方式實現出來,給大家一個更直觀的呈現。(注意:本框架本身不夠完善,還不足以用於商用業務開發,代碼可能存在缺陷,部分功能還有優化空間;同時要說明,Struts2、Spring的實際實現較爲複雜(自然功能也強大),本框架借鑑中他們本來的一些實現方式,但也做了較大改動和簡化)

二、涉及到的主要技術點

1.MVC三層分離(參考資料:Struts2與MVC基礎入門

 1)M(Model)爲業務處理邏輯處理及持久化操作等

 2)V (View)爲頁面呈現,本架構中通過JSP呈現,Struts2中還可以通過VelocityFreemarker來做展示。

 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





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章