本文来自 fair-jm.iteye.com 转截请注明出处
用了下JPA对于其方便的操作很好奇 通过继承CRUDRepository等接口 DAO不用写实现类就可以在注入后实现基本的增删改查功能
搜索到了一些内容:
http://my.oschina.net/xdev/blog/126049 这里有一些实现的原理 比较详细
http://sunting-bcwl.iteye.com/blog/768989 代码实现如何获得泛型等内容
实现很简单 用了JPA和java的动态代理以及反射
基本的结构
有一个CRUD的接口:
package jdbc; import java.util.List; public interface CRUD<T,I> { public List<T> query(); public T findOne(I id); public void delete(I id); public void add(T t); public int update(T t); }
此接口是所有DAO的接口必须继承的
实践中用了两个DAO user和item
这里以User为例
User的代码如下:
package com.cc.proxy.test.domain; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="user") public class User { @Id private int uid; private String name; private int sex; private String description; private int age; //省略getter和setter }
UserDao:
package com.cc.proxy.test.dao; import com.cc.proxy.test.domain.User; import jdbc.CRUD; public interface UserDao extends CRUD<User,Integer> { }
通过代理User就可以使用CRUD等方法(我实际上就写了query和add 其他方法在代理的invoke中补充就可以了):
=================================================================================
=================================================================================
接下去是最主要的代理类的代码:
代理类ProxyDao(实现InvocationHandler)的属性如下:
private String tableName=null; //表名 private Class entity=null; //实体类 private Class idType=null; //id类型 private String id=null; //id在实体类中的属性名(方便操作) //filed<--->column private Map<String,String> columMapping=new HashMap<>();
构造方法:
public ProxyDao(Class dao) throws Exception{ if(!check(dao)){ throw new Exception("not a dao"); } }
其中check方法是完成DAO的检查
这里的检查主要包括:
- DAO是否继承了CRUD接口
- CRUD接口上的泛型是否是实体(有无@Entity和@Id)
代码如下:
private boolean check(Class dao) throws Exception{ boolean hasCRUD=false; //检查是否继承了CRUD接口 没有的话就返回false for(Class inter:dao.getInterfaces()){ if(inter==CRUD.class){ hasCRUD=true; break; } } if(!hasCRUD){ return false; } //取得第一个接口的信息 这里也就是CRUD接口 Type t=dao.getGenericInterfaces()[0]; //获得CRUD的泛型 第一个是对应的实体类 第二个是对应的id类型 entity=(Class)((ParameterizedType)t).getActualTypeArguments()[0]; idType=(Class)((ParameterizedType)t).getActualTypeArguments()[1]; Annotation[] as=entity.getAnnotations(); //检查是否有Entity这个annotation没有则退出 boolean hasEntity=false; for(Annotation a:as){ if(a.annotationType()==javax.persistence.Entity.class){ hasEntity=true; } if(a.annotationType()==javax.persistence.Table.class){ tableName=((javax.persistence.Table)a).name(); } } if(!hasEntity){ return false; } //如果table的name没有值 那么用实体类的类名就可以了 if(tableName==null){ tableName=entity.getSimpleName(); } boolean hasId=false; Field[] fs=entity.getDeclaredFields(); for(Field f:fs){ if(f.getAnnotation(Id.class)!=null){ id=f.getName(); hasId=true; } //这里建立 属性<-->字段名的映射 if(f.getAnnotation(Column.class)!=null){ columMapping.put(f.getName(), f.getAnnotation(Column.class).name()); }else{ columMapping.put(f.getName(),f.getName()); } } if(!hasId){ return false; } return true; }
最后就是invoke方法了,invoke完成运行方法的指派:
public static final String QUERY="select * from %s "; public static final String ADD="insert into %s(%s) values (%s)"; public static final String DELETE="delete from %s where %s=?"; public static final String FIND="select * from %s where %s=?"; public static final String UPDATE="update %s set %s where %s=?"; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("query")){ List list=new ArrayList<>(); try(Connection conn=DBConnection.getConnection()){ PreparedStatement pst=conn.prepareStatement(String.format(QUERY, tableName)); ResultSet rs=pst.executeQuery(); while(rs.next()){ Object o=entity.newInstance(); for(Field f:entity.getDeclaredFields()){ f.setAccessible(true); f.set(o, rs.getObject(columMapping.get(f.getName()),f.getType())); } list.add(o); } } return list; } if(method.getName().equals("add")){ Object o=args[0]; StringBuffer arg=new StringBuffer(); StringBuffer values=new StringBuffer(); for(Field f:entity.getDeclaredFields()){ if(f.getName().equals(id)){ //id不用自己插入 continue; } f.setAccessible(true); if(f.get(o)!=null){ arg.append(columMapping.get(f.getName())).append(","); values.append(String.format("'%s'", f.get(o))).append(","); } } // arg.substring(0,arg.length()-1); // values.substring(0,values.length()-1); try(Connection conn=DBConnection.getConnection()){ PreparedStatement pst=conn.prepareStatement(String.format(ADD, tableName,arg.substring(0,arg.length()-1),values.substring(0,values.length()-1))); pst.execute(); } } if(method.getName().equals("delete")){ Object o=args[0]; try(Connection conn=DBConnection.getConnection()){ PreparedStatement pst=conn.prepareStatement(String.format(DELETE, tableName,id)); pst.setObject(1, o); pst.execute(); } } if(method.getName().equals("findOne")){ Object o=args[0]; try(Connection conn=DBConnection.getConnection()){ PreparedStatement pst=conn.prepareStatement(String.format(FIND, tableName,id)); pst.setObject(1, o); ResultSet rs=pst.executeQuery(); if(rs.next()){ Object ob=entity.newInstance(); for(Field f:entity.getDeclaredFields()){ f.setAccessible(true); f.set(ob, rs.getObject(columMapping.get(f.getName()),f.getType())); } return ob; }else{ return null; } } } if(method.getName().equals("update")){ Object o=args[0]; Object idNumber=null; StringBuffer set=new StringBuffer(); for(Field f:entity.getDeclaredFields()){ f.setAccessible(true); if(f.getName().equals(id)){ idNumber=f.get(o); continue; } if(f.get(o)!=null){ set.append(columMapping.get(f.getName())+"="+String.format("'%s'", f.get(o))).append(","); } } if(idNumber==null){ return 0; } try(Connection conn=DBConnection.getConnection()){ PreparedStatement pst=conn.prepareStatement(String.format(UPDATE, tableName,set.substring(0,set.length()-1),id)); pst.setObject(1, idNumber); return pst.executeUpdate(); } } return null; }
不只是这些方法 对于UserDao如果有findOneByName这种方法 完全可以通过invoke方法进行分析和执行相应动作(这里可以用一个判断方法名是findOneBy开头的来 后面的属性放入SQL中就可以了)
代码中偷懒直接对属性用Field来取 实际中应该通过getter和setter来取和赋值 自己实验一下就怎么方便怎么写了
实现起来不是很难 就是一些基本的反射的操作和一些基础的逻辑判断
从前也未思考过动态代理有什么用(最多就是一些AOP的内容) 对于直接用来实现接口 压根就没想过..
写代码这种东西 总是能找到新的乐趣啊....
此外关于动态代理的其他的用法可以看:
http://www.infoq.com/cn/articles/cf-java-reflection-dynamic-proxy
infoQ里的内容挺不错的~~~
2013最后一篇博客了~~来年也要玩得开心^_^~~