写在前面:
泛型与反射是java中的两种强大机制,可以很好的提高代码的灵活性和复用性。本篇文章向大家展现在JDBC和Servlet编程场景下反射和泛型技术的实践。通过灵活使用这两种机制打造
高度可复用的JDBC和Servlet代码!
1.JDBC中的实践
使用JDBC连接数据库后我们一般要编写方法对数据库进行CRUD操作。一般的我们需要编写两个方法:
-
一个负责查询操作,传入SQL,返回实体类对象的列表(对应数据表中的多行)
-
一个负责增删改操作,传入SQL,返回一个int值表示影响数据表中的行数
仔细思考查询操作对应的方法就会引出一些问题。我们可以发现,对于不同数据表,表的字段数量不同,各字段的数据类型不同,名称不同,我们需要构造的实体类对象不同,返回的对象列表类型也不同......这种种的不同就为查询方法的编写带来了麻烦,难道我们需要为每个表的查询都编写一个查询方法吗?对只有1,2个表的情况当然可以这样做,但当表的数量较多时这显然不是一个可取的做法。
我们期望只写一个高度通用的查询方法,能应对所有数据表的查询(查询一个表中的若干行)。使用泛型与反射机制可以实现这一目的!泛型机制主要用来在方法内部分析实体类对象结构,以及创建实体类对象。
/**
* 通用的数据表查询方法
* @param clazz 实体类.class。在该方法中使用反射机制分析其结构
* @param sql SQL语句结构
* @param args SQL语句参数
* @return 查询结果列表
* @param <T> 实体类类型
*/
protected <T> List<T> baseQuery(Class clazz, String sql, Object ... args){
//将要返回的实体类对象列表
List<T> list =new ArrayList<>();
//获取JDBC连接,可专门写一个类对连接进行管理
Connection connection = ConnectionManager.getConnection();
PreparedStatement preparedStatement=null;
ResultSet resultSet =null;
try {
// SQL语句对象
preparedStatement = connection.prepareStatement(sql);
// 设置SQL语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1,args[i]);
}
// 执行数据表查询
resultSet = preparedStatement.executeQuery();
//获取列信息(有多少列)
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
// 将结果集通过反射封装成实体类对象
while (resultSet.next()) { //while循环遍历行
//使用反射实例化对象
Object obj =clazz.getDeclaredConstructor().newInstance();
for (int i = 1; i <= columnCount; i++) { //for循环遍历列
//属性名
String columnName = metaData.getColumnLabel(i);
//获取属性值
Object value = resultSet.getObject(columnName);
//实体类字段名与表中属性名一一对应,通过反射获取之
Field field = clazz.getDeclaredField(columnName);
//绕开访问权限
field.setAccessible(true);
//实体类字段赋值
field.set(obj,value);
}
//加入要返回的实体类对象列表
list.add((T)obj);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//资源回收
if (null !=resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
ConnectionManager.releaseConnection(connection);
}
return list;
}
2.Servlet中的实践
我们使用Servlet类的惯用方式是,自定义一个继承HttpServlet类的类,重写父类的service方法(protected修饰的)(重写doGet/doPost也可以),方法体中编写对请求的处理逻辑。在MVC软件架构模式下,Servlet类一般出现在Controller层。
现在我们构想这样的一个场景,前端需要向后端发请求进行数据库的CRUD操作。试想这样的场景下应该怎样编写Servlet类。为每种操作各编写一个Servlet类并分别配置,让前端去请求不同的Servlet类获取服务?这应该是最容易想到的一种写法,但显然配置这么多Servlet类显然不够科学。现在我们
利用反射机制进行优化。
首先我们创建一个BaseController类,它继承HttpServlet,并重写service方法
public class BaseController extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求映射路径
String requestURI=req.getRequestURI();
//获取请求映射路径中的统配符部分/experiment/*,本示例中*的值为add,del,alter,query
//统配符部分的名称与子类中方法名对应
String[] split=requestURI.split("/");
String methodName=split[split.length-1];
//使用反射机制进行方法调用
Class clazz=this.getClass();
try{
Method declaredMethod=clazz.getDeclaredMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);
declaredMethod.setAccessible(true);
declaredMethod.invoke(this,req,resp);
}catch (Exception ex){
ex.printStackTrace();
}
}
}
然后创建一个Controller类它继承自BaseController
@WebServlet("/experiment/*") //add del alter query
public class Controller extends BaseController {
private Service service=new Service();
public void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {...}
public void del(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {...}
public void alter(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {...}
public User query(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {...}
}
有两个关键点要尤为注意:
-
Controller类注解中请求映射路径的写法
-
Controller类中方法的名称
映射路径需要写为这种两级路径形式
/experiment/*
,并且第二级采用通配的形式。现在Controller类可以匹配以/experiment/开头的请求,例如/experiment/add,/experiment/del...在本示例设置的场景下,客户端的增删改查请求分别对应/experiment/add,/experiment/del,/experiment/alter,/experiment/query路径,都会匹配到Controller类。注意Controller类是继承自BaseController类的,所以这些不同的请求都会先汇聚到BaseController类的service方法中。
设计的精髓在于,在BaseController的service方法中利用反射机制决定调用子类Controller中的哪个方法处理请求
。为达到这一目的,Controller类中方法的名字就不能乱起,必须与请求路径中的统配符的具体值一一对应。因为在BaseController中就是通过提取请求路径中的统配符值作为方法名使用反射机制进行方法调用的!
好了,现在大家回过头看看上面的两段代码,应该是比较清晰喽!
当然以上这些写法只是为简单场景提供了方便,复杂场景下可能还是需要改进。例如JDBC示例中反射的频繁使用可能会在数据量大时产生性能问题。但是这两个例子对于加深java反射机制的理解以及融汇贯通JDBC, Servlet这样的原生技术还是有很大帮助的