手写SpringMVC框架

   在学习完Spring框架之后, 我们知道了 Spring容器, IOC, AOP, 事务管理, SpringMVC 这些Spring的核心内容, 也知道了它们底层实现的基本原理, 比如Spring容器就是个Map映射, IOC底层就是反射机制, AOP底层是动态代理, SpringMVC不就是对Servlet进行了下封装嘛! 哈哈, 当然这些只是些皮毛, Spring除此之外还有更加复杂的设计, 但我们完全可以抛弃那些复杂的设计, 通过这些底层原理自己来写个Spring框架. 写完之后, 相信我们会对Spring框架有个更加深刻的理解.
在开始动手写博客之前, 我已经完成了自己的Spring框架, 并给它取名为Aution。项目导包如下:

在IDEA中新建一个独立的Javax项目开发同数据访问模块。其包主要结构及主要文件各文件的作用如下。

(1)DatabaseUtil类是数据类连接管理类,包含创建连接、销毁链接等方法。

(2)ConfigManager类扶着读取保存有数据库连接参数的配置文件,配置文件为默认存储在classpath根目录下的database.properties文件

(3)CommonDao类定义通用的数据访问方法,并对查询条件和查询结果的处理进行封装。

(4)TypeConstant类定义了Java数据类型与数据库数据的对应关系,以便在CommonDao中进行自动的数据处理。

 

开始

一:建立框架

首先创建一个Maven项目,取名为Aution,在pom.xml中添加jar包

<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>
    <!-- JSP标准标签库 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
        <scope>runtime</scope>
    </dependency>
    <!-- MySQL -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.33</version>
        <scope>runtime</scope>
    </dependency>
    <!--数据库连接池-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-dbcp2</artifactId>
        <version>2.0.1</version>
    </dependency>
    <!--JDBC工具类库-->
    <dependency>
        <groupId>commons-dbutils</groupId>
        <artifactId>commons-dbutils</artifactId>
        <version>1.6</version>
    </dependency>
    <!-- 日志框架 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.7</version>
    </dependency>
    <!-- 动态代理依赖 -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>2.2.2</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>
    <!--JSON依赖-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.49</version>
    </dependency>
</dependencies>

二:创建web工程目录结构如下

 

在web工程目录建立css包、images包和js包。css:存放CSS文件 ;images:存放图片文件;js:存放JS文件。

初识化的流程如下:

初始化流程共有五个:扫描文件,对象实例化,aop代理对象生成,url与方法关联,变量注入。

扫描文件:                                                                                                    对象实例化:分为类名集合、反射生成实例,并存储对象的方法信息以及注解识别,判断实例化类型,目前有Controller、service和aops                                                                                                                        三种

代理对象的优先级:先生成方法级别的代理,再生成类级别的代理。执行起来表现为:先执行类级别的代理,再执行方法级别的代理。aop代理对象生成分为六种:遍历所有的Bean,aop注解识别,识别类级别aop注解、遍历Bean所有的方法、代理对象生成。

url与方法关联,如图所示:

url与方法关联分为遍历所有的Controller类型的Bean,构建url、并与方法建立映射和识别类型级和方法的RequestMapper注解。

变量注入,如图所示:

三:数据库连接与关闭数据工具类DatebaseUtil代码

 

package my.framework.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * 数据库连接与关闭工具类。
 */
public class DatabaseUtil {
	private static String driver = ConfigManager.getProperty("driver");// 数据库驱动字符串
	private static String url = ConfigManager.getProperty("url");// 连接URL字符串
	private static String user = ConfigManager.getProperty("user"); // 数据库用户名
	private static String password = ConfigManager.getProperty("password"); // 用户密码

	static {
		try {
			Class.forName(driver);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private static final ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

	/**
	 * 获取数据库连接对象。
	 * 
	 * @throws SQLException
	 */
	public static Connection getConnection() throws SQLException {
		// 获取连接并捕获异常
		Connection connection = threadLocal.get();
		if (connection == null || connection.isClosed())
			try {
				connection = DriverManager.getConnection(url, user, password);
				if (connection.getAutoCommit())
					connection.setAutoCommit(false);
				threadLocal.set(connection);
			} catch (SQLException e) {
				e.printStackTrace();
				throw e;
			}
		return connection;// 返回连接对象
	}

	/**
	 * 关闭语句容器。
	 * 
	 * @param stmt
	 *            Statement对象
	 * @param rs
	 *            结果集
	 */
	public static void closeStatement(Statement stmt, ResultSet rs) {
		// 若结果集对象不为空,则关闭
		try {
			if (rs != null && !rs.isClosed())
				rs.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 若Statement对象不为空,则关闭
		try {
			if (stmt != null && !stmt.isClosed())
				stmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 关闭数据库连接。
	 */
	public static void closeConnection() {
		Connection connection = (Connection) threadLocal.get();
		threadLocal.set(null);

		try {
			if (connection != null && !connection.isClosed())
				connection.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

}

实现数据库类型与Java类型的映射(TypeConstant)代码:

package my.framework.dao;

import java.util.HashMap;
import java.util.Map;

/**
 * 实现数据库类型与Java类型的映射
 */
public class TypeConstant {
	/***
	 * 数据库类型和Java类型映射,key为数据库类型,value为Java类型
	 */
	private static Map<String, Class<?>> typeMap = new HashMap<String, Class<?>>();

	static {
		typeMap.put("BIGINT", Long.class);
		typeMap.put("INT", Integer.class);
		typeMap.put("VARCHAR", String.class);
		typeMap.put("TEXT", String.class);
		typeMap.put("DATETIME", java.util.Date.class);
		typeMap.put("DECIMAL", Double.class);
		typeMap.put("TINYINT", Integer.class);
		typeMap.put("BIT", Boolean.class);
		typeMap.put("TIMESTAMP", java.util.Date.class);
	}

	public static void addType(String columnType, Class<?> javaType) {
		typeMap.put(columnType, javaType);
	}

	public static Class<?> getJavaType(String columnType) {
		return typeMap.get(columnType);
	}
}

执行数据库操作的工具类(CommonDao)代码

package my.framework.dao;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import my.framework.dao.DatabaseUtil;

/**
 * 执行数据库操作的工具类。
 */
public class CommonDao {

	/**
	 * 增、删、改操作
	 * 
	 * @param sql
	 *            sql语句

	 *            参数数组
	 * @return 执行结果
	 * @throws SQLException
	 */
	public int executeUpdate(String sql, Object[] params) throws SQLException {
		int result = 0;
		PreparedStatement pstmt = null;
		try {
			pstmt = DatabaseUtil.getConnection().prepareStatement(sql);
			if (!(params == null || params.length == 0))
				for (int i = 0; i < params.length; i++) {
					pstmt.setObject(i + 1, params[i]);
				}
			result = pstmt.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
			throw e;
		} finally {
			DatabaseUtil.closeStatement(pstmt, null);
		}
		return result;
	}

	public int executeUpdate(String sql, Object entity) throws SQLException {
		Map<String, Object[]> mappingResult = handleParameterMapping(sql, entity);
		Entry<String, Object[]> entry = mappingResult.entrySet().iterator().next();
		return executeUpdate(entry.getKey(), entry.getValue());
	}

	protected Map<String, Object[]> handleParameterMapping(String sql, Object entity) throws SQLException {
		Map<String, Object[]> mappingResult = new HashMap<String, Object[]>();
		List<String> placeholders = new ArrayList<String>();
		int offset = 0;
		while (true) {
			int start = sql.indexOf("#{", offset);
			if (start >= offset) {
				int end = sql.indexOf("}", start);
				placeholders.add(sql.substring(start + 2, end));
				offset = end + 1;
			} else {
				break;
			}
		}
		if (placeholders.size() > 0) {
			Object[] params = new Object[placeholders.size()];
			for (int i = 0; i < placeholders.size(); ++i) {
				String placeholder = placeholders.get(i);
				sql = sql.replaceFirst("\\#\\{" + placeholder + "\\}", "?");
				try {
					Method getter = entity.getClass()
							.getMethod("get" + placeholder.substring(0, 1).toUpperCase() + placeholder.substring(1));
					Object param = getter.invoke(entity);
					params[i] = param;
				} catch (NullPointerException | NoSuchMethodException | SecurityException | IllegalAccessException
						| IllegalArgumentException | InvocationTargetException e) {
					throw new RuntimeException("无法为占位符 #{" + placeholder + "} 赋值!", e);
				}
			}
			System.out.println("===================================SQL: " + sql); // log
			String ps = "[\n";
			for (Object p : params) ps += "\t" + p + "\n";
			ps += "]";
			System.out.println("===================================Params: \n" + ps); // log
			mappingResult.put(sql, params);
		} else {
			mappingResult.put(sql, new Object[] {});
		}
		return mappingResult;
	}

	/**
	 * 查询操作
	 * 
	 * @param sql
	 *            sql语句
	 * @param params
	 *            参数数组
	 * @return 查询结果
	 * @throws SQLException
	 */
	public <T> List<T> executeQuery(Class<T> clz, String sql, Object[] params) throws SQLException {
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = DatabaseUtil.getConnection().prepareStatement(sql);
			if (!(params == null || params.length == 0))
				for (int i = 0; i < params.length; i++) {
					pstmt.setObject(i + 1, params[i]);
				}
			rs = pstmt.executeQuery();

			return handleResultMapping(clz, rs);
		} catch (SQLException e) {
			e.printStackTrace();
			throw e;
		} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
				| InvocationTargetException e) {
			throw new RuntimeException("封装查询结果时出现错误!", e);
		} finally {
			DatabaseUtil.closeStatement(pstmt, rs);
		}
	}

	public <T> List<T> executeQuery(Class<T> clz, String sql, Object entity) throws SQLException {
		Map<String, Object[]> mappingResult = handleParameterMapping(sql, entity);
		Entry<String, Object[]> entry = mappingResult.entrySet().iterator().next();
		return executeQuery(clz, entry.getKey(), entry.getValue());
	}

	@SuppressWarnings("unchecked")
	protected <T> List<T> handleResultMapping(Class<T> clz, ResultSet rs) throws SQLException, InstantiationException,
			IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		if (clz.equals(Integer.class)) {
			List<Integer> result = new ArrayList<Integer>();
			while (rs.next())
				result.add(rs.getInt(1));
			return (List<T>) result;
		}
		ResultSetMetaData metaData = rs.getMetaData();
		int count = metaData.getColumnCount();
		if (clz.equals(Object[].class)) {
			List<Object[]> result = new ArrayList<Object[]>();
			Object[] row = null;
			while (rs.next()) {
				row = new Object[count];
				for (int i = 0; i < count; ++i) {
					Class<?> targetClz = TypeConstant.getJavaType(metaData.getColumnTypeName(i + 1));
					if (targetClz.equals(java.util.Date.class))
						row[i] = new java.util.Date(rs.getTimestamp(i + 1).getTime());
					else
						row[i] = rs.getObject(i + 1, targetClz);
				}
				result.add(row);
			}
			return (List<T>) result;
		}
		Method[] setters = new Method[count];
		Class<?>[] types = new Class[count];
		for (int i = 0, j = 1; i < count; ++i, ++j) {
			String lable = metaData.getColumnLabel(j);
			String type = metaData.getColumnTypeName(j);
			try {
				setters[i] = clz.getMethod("set" + lable.substring(0, 1).toUpperCase() + lable.substring(1),
						TypeConstant.getJavaType(type));
				types[i] = TypeConstant.getJavaType(type);
			} catch (NoSuchMethodException | SecurityException e) {
				e.printStackTrace(); // log
				setters[i] = null;
				types[i] = null;
			}
		}
		List<T> result = new ArrayList<T>();
		T instance = null;
		while (rs.next()) {
			instance = clz.newInstance();
			for (int k = 0; k < count; ++k) {
				if (setters[k] == null)
					continue;
				else {
					System.out.println("===========================setter: " + setters[k].getName() + "( " + types[k].getTypeName() + " )");
					System.out.println("===========================param: " + (rs.getObject(k+1)==null?null:rs.getObject(k+1).getClass()));
					if (types[k].equals(java.util.Date.class))
						setters[k].invoke(instance, new java.util.Date(rs.getTimestamp(k + 1).getTime()));
					else
						setters[k].invoke(instance, rs.getObject(k + 1, types[k]));
				}
			}
			result.add(instance);
		}
		return result;
	}
}

四:在classpath跟路径添加XML格式配置文件,默认命名为myweb.xml)

<?xml version="1.0" encoding="UTF-8"?>
<doublegui>
    <controllers>
    <controller path="/auction" class="cn.auction.web.AuctionController">
    <method path="/addAuction">addAuction</method>
        <method path="/auctionBid">addAuctionRecord</method>
        <method path="/auctionDetail">showAuctionDetail</method>
        <method path="/auctionList">showAuctionList</method>
        <method path="/auctionResult">showActionResult</method>
    </controller>
    <controller path="/user" class="cn.auction.web.UserController">
        <method path="/doRegister">doRegister</method>
        <method path="/doLogin">doLogin</method>
        <method path="/doAdminLoad">doAdminLoad</method>
        <method path="/doLogout">doLogout</method>
        <method path="/findById">findById</method>
    </controller>
    </controllers>
    <servlet>
        <servlet-name>myweb</servlet-name>
        <servlet-class>my.framework.web.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>myweb</servlet-name>
        <url-pattern>/do/*</url-pattern>
    </servlet-mapping>
</doublegui>

五:在IDEA中新建一个独立的JavaWeb项目以开发模块,以对请求处理模式和响应式处理模式进行规范。

myweb.xml代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<doublegui>
    <controllers>
    <controller path="/auction" class="cn.auction.web.AuctionController">
    <method path="/addAuction">addAuction</method>
        <method path="/auctionBid">addAuctionRecord</method>
        <method path="/auctionDetail">showAuctionDetail</method>
        <method path="/auctionList">showAuctionList</method>
        <method path="/auctionResult">showActionResult</method>
    </controller>
    <controller path="/user" class="cn.auction.web.UserController">
        <method path="/doRegister">doRegister</method>
        <method path="/doLogin">doLogin</method>
        <method path="/doAdminLoad">doAdminLoad</method>
        <method path="/doLogout">doLogout</method>
        <method path="/findById">findById</method>
    </controller>
    </controllers>
    <servlet>
        <servlet-name>myweb</servlet-name>
        <servlet-class>my.framework.web.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>myweb</servlet-name>
        <url-pattern>/do/*</url-pattern>
    </servlet-mapping>
</doublegui>

database.properties连接数据配置:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/onlineauctionsystem?useUnicode=true&characterEncoding=utf-8
user=root
password=123456

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值