利用反射和动态代理实现DAO接口的默认CRUD实现(类似Sprng JPA)

本文来自 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中补充就可以了):

   
利用反射和动态代理实现DAO接口的默认CRUD实现(类似Sprng JPA)_第1张图片
 

=================================================================================

=================================================================================

 

接下去是最主要的代理类的代码:

代理类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的检查

这里的检查主要包括:

  1. DAO是否继承了CRUD接口
  2. 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最后一篇博客了~~来年也要玩得开心^_^~~

你可能感兴趣的:(动态代理)