在学习完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