提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
前言
这是一个简单的手写SpringMVC框架
提示:以下是本篇文章正文内容,下面案例可供参考
一、SpringMVC是什么?
二、代码实现
1.通用数据访问模块
·DatabaseUtil
数据库连接管理类,包含了连接、销毁连接等方法。
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 = ConfigManagers.getProperty("driver");// 数据库驱动字符串
private static String url = ConfigManagers.getProperty("url");// 连接URL字符串
private static String user = ConfigManagers.getProperty("user"); // 数据库用户名
private static String password = ConfigManagers.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();
}
}
}
1.初始化一个ThreadLocal对象,有get()、set()方法。
2.get()方法返回当前线程的线程内部变量,即Connection对象
3.如果当前线程是初次访问数据库,Connection对象为NULL,则创建一个Connection对象
4.把创建的Connection对象保存到ThreadLocal中,即和当前线程绑定。则当该线程在同一次请求中再次访问数据库时,从ThreadLocal中get()就可以获取刚才使用过的Connection对象。
5.业务流程的最后,关闭Connection时,首先调用ThreadLocal的get()方法获取和当前线程绑定的Connection对象,接着调用set()方法,把ThreadLocal管理的Connection对象置为NULL,从而执行了对当前线程的清理工作,为使用该线程的清理工作,为使用该线程处理另一个用户请求做好准备。
·TypeConstant
定义Java数据类型与数据库类型的对应关系,以便CommonDao中进行自动的数据处理
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语句
* @param prams
* 参数数组
* @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;
}
}
·ConfigManager
负责读取保存有效数据库连接参数的配置文件,配置文件默认为存储在classpath根目录下的database.properties文件。
package my.framework.dao;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ConfigManagers {
private static Properties props = null;
static {
InputStream is = null;
is = ConfigManagers.class.getClassLoader().getResourceAsStream(
"database.properties");
if (is == null)
throw new RuntimeException("找不到数据库参数配置文件!");
props = new Properties();
try {
props.load(is);
} catch (IOException e) {
throw new RuntimeException("数据库配置参数加载错误!", e);
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static String getProperty(String key) {
return props.getProperty(key);
}
}
开发完毕测试,将以上Java类打包成jar文件,即可在其他项目中作为通用数据访问组件引入并使用,从而简化项目中数据访问模块的编码。
2.请求分发模块
在IDEA中新建一个独立的Java Web项目以开发请求分发模块,以对请求处理模式和响应处理模式进行规范。
·DispatcherServlet 类根据请求URL包含的映射信息调用对应功能模块处理请求。
·ControllerMapping 类封装功能模块类型信息,包括类名和方法名两部分信息。
·ControllerMappingManager 类加载映射配置文件,提取URL映射和模块方法的对应关系。
·PrintUtil 类负责输出JSON格式的响应。
·ConfigLoadingException 异常表示加载映射配置文件错误。
·ConfigException 异常表示映射配置文件存在问题,如未提供类名、方法名。
·PathException 异常表示请求URL包含的映射路径存在问题,找不到对应处理方法。
·ViewPathException 异常表示导航路径存在问题,如没有以"/"开头。
总结
项目暂未开发完善,如有想要全部代码,请一键三连,可能不在就是饿死了。