JAVA框架封装(五):解析类的设计

项目已经上传到了github上面,地址:https://github.com/kingzs/kingeasy,有兴趣的伙伴,可以一起开发。

每个实体类的注解,都需要解析出来,才可以使用。那么,就可以定义一个解析类,专门用来存放实体类的注解解析之后的信息,这个类就是解析类。在通用的增删查改方法里面,如果需要用到类的解析信息,只需要到解析类里拿就可以了。解析类的实现代码:

package org.kingeasy.base;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.kingeasy.annotation.ColumnName;
import org.kingeasy.annotation.Immutable;
import org.kingeasy.annotation.Mapping;
import org.kingeasy.annotation.NoDeletion;
import org.kingeasy.annotation.NoRepeat;
import org.kingeasy.annotation.PrimaryKey;
import org.kingeasy.annotation.Search;
import org.kingeasy.annotation.Searches;
import org.kingeasy.annotation.Statistic;
import org.kingeasy.annotation.TableName;

/**
 * 解析类
 * 一个类对应一个解析类
 * 这个解析类,就是把类里的各个注解给解析出来并保存
 * 在通用的增删查改方法里面,就是到这个类里拿相应的信息进行操作
 */

public class AnalysisClass {

	/**
	 * 类所对应的表名
	 * 如果类添加了注解@TableName,则取注解的value作为表名
	 * 如果没有添加这个注解,则取类的简短名称并全转成小写,作为表名
	 */
	private String tableName;
	
	/**
	 * 类的主键字段名
	 * 也就是类添加了@PrimaryKey的成员变量的变量名
	 * 现在,暂时只支持一个类,只有一个主键的情况
	 */
	private String primaryKey;
	
	/**
	 * 类的主键对应表中的列名
	 * 也就是@PrimaryKey注解的value值
	 * 同时,@PrimaryKey注解也可以不添加value值,则默认为空字符串,那么就取字段的名称作为表的列名
	 */
	private String tablePrimaryKey;
	
	/**
	 * 类的字段名与表的列名的映射
	 * 如果字段名添加了@ClomnName注解,则会把注解的value作为值,把字段名作为键,存入fieldColumnMap中
	 * 如果字段名没有添加这个注解,则键和值都为字段名,存入fieldColumnMap中
	 * 同时,@PrimaryKey注解的映射,也会存入这个map中
	 */
	private Map<String, String> fieldColumnMap = new HashMap<>();
	
	/**
	 * 表的列名与类的字段名的映射
	 * 与上一个map,只是键和值反过来存而已
	 */
	private Map<String, String> columnFieldMap = new HashMap<>();
	
	/**
	 * 不能重复的字段映射
	 * 只有字段添加了@NoRepeat注解,才会把字段名作为键,字段的value作为值,存入这个map里面
	 */
	private Map<String, String> noRepeatMap = new HashMap<>();
	
	/**
	 * 可以修改的字段集合
	 * 没有添加@Immtable注解的字段,才会加入到这个map里面
	 */
	private Set<String> modifiableField = new HashSet<>();
	
	/**
	 * 限制删除的字段映射
	 * 添加了@NoDeletion注解的字段,字段名作键,注解的value作值,存入这个map
	 */
	private Map<String, String> noDeletionMap = new HashMap<>();
	
	/**
	 * 本类查询条件映射
	 * 这个map只存放当前类里添加的@Search注解
	 * 以注解的param作为键,以字段对应的表的列名,加上前缀"t0.",再拼接注解的exp,作为值,存入这个map中
	 * 如果没有param的值,则默认为空字符串,那就以字段的名称作为键存入map中
	 */
	private Map<String, String> selfSearchExpMap = new HashMap<>();
	
	/**
	 * 来自于其它类的查询条件映射
	 * 这个map用于存放所有来自于其它关联的类里的@Search注解的解析数据
	 * 存放的内容,与本类的查询条件唯一不同的值里的前缀,本类的都以"t0."作为前缀
	 * 而这里的前缀,以字段在类的定义里的顺序乘以10得到,比如,字段在类的定义里的顺序是在第6,那么前缀就是"t60."
	 * 在通用的查询方法里面,就是用t60作为关联类对应的表的别名
	 */
	private Map<String, String> otherSearchExpMap= new HashMap<>();
	
	/**
	 * 统计注解映射
	 * 键是字段名,值是解析后的聚合函数表达式
	 */
	private Map<String, String> statisticsMap = new HashMap<>();
	
	/**
	 * 字段与类的映射
	 * 当一个字段是关联的另一个类的时候,就会解析到这个map里面保存起来
	 * 键是字段名,值是另一个类的类文件
	 * 注:类文件是通过泛型来获取的,所以必须要使用泛型,才可以正确得到解析
	 * 比如字段定义为:private List<Item> items;就可以通过字段的定义,来获取Item类
	 * 如果写成:private List Items;那么是没法知道这个字段是关联哪个类的
	 */
	private Map<String, Class<?>> fieldAsClassMap = new HashMap<>();
	
	/**
	 * 在@Mapping注解里,value是一个字符串数组,这个数据,用了一个类来解析,就是MapClass
	 * 这个map就是存放字段与MapClass的映射关系的
	 */
	private Map<String, MapClass> mapClassMap = new HashMap<>();
	
	/**
	 * 字段与字段的序号的映射
	 * 字段关联了另一个类,多表关联查询时,另一个表取别名,需要通过这个序号来指定
	 */
	private Map<String, Integer> fieldIndexMap = new HashMap<>();
	
	/**
	 * 关联其它类,其它类的查询条件可能有多个,所以其它类的查询条件的键,要放到一个集合里面
	 * 以字段的序号为键,以集合为值,保存到这个map里面
	 */
	private Map<Integer, Set<String>> indexForOtherSearchList = new HashMap<>();
	
	public AnalysisClass(){}
	
	public AnalysisClass(Class<?> clazz){
		analysisClass(clazz);
	}
	
	public void analysisClass(Class<?> clazz){
		setTableName(clazz);
		Field[] fields = clazz.getDeclaredFields();
		for(Field field : fields){
			String fieldName = field.getName();
			setPrimaryKey(field, fieldName);
			putColumnAsField(field, fieldName);
			putStatistics(field, fieldName);
			putSelfSearch(field, fieldName);
			
			if(field.isAnnotationPresent(NoRepeat.class)){
				noRepeatMap.put(fieldName, field.getDeclaredAnnotation(NoRepeat.class).value());
			}
			if(!field.isAnnotationPresent(Immutable.class) && !field.isAnnotationPresent(PrimaryKey.class) && 
					!field.isAnnotationPresent(Mapping.class)){
				modifiableField.add(fieldName);
			}
			if(field.isAnnotationPresent(NoDeletion.class)){
				noDeletionMap.put(fieldName, field.getDeclaredAnnotation(NoDeletion.class).value());
			}
		}
	}
	
	private void putSelfSearch(Field field, String fieldName){
		if(field.isAnnotationPresent(Searches.class)){
			Search[] searchs = field.getDeclaredAnnotationsByType(Search.class);
			for(int i=0,len=searchs.length; i<len; ++i){
				Search search = searchs[i];
				String param = search.param();
				if("".equals(param)){
					selfSearchExpMap.put(fieldName, "t0." + fieldColumnMap.get(fieldName) + " " + search.exp());
				}else{
					selfSearchExpMap.put(param, "t0." + fieldColumnMap.get(fieldName) + " " + search.exp());
				}
			}
		}
		if(field.isAnnotationPresent(Search.class)){
			Search search = field.getDeclaredAnnotation(Search.class);
			String param = search.param();
			if("".equals(param)){
				selfSearchExpMap.put(fieldName, "t0." + fieldColumnMap.get(fieldName) + " " + search.exp());
			}else{
				selfSearchExpMap.put(param, "t0." + fieldColumnMap.get(fieldName) + " " + search.exp());
			}
		}
	}
	
	public void setOtherSearch(Class<?> clazz){
		Field[] fields = clazz.getDeclaredFields();
		int index = 0;
		for(Field field : fields){
			index += 10;
			if(field.isAnnotationPresent(Mapping.class)){
				Mapping mapping = field.getDeclaredAnnotation(Mapping.class);
				switch(mapping.tableRelation()){
				case ONE_TO_ONE:
					
					break;
				case ONE_TO_MANY:
					
					break;
				case MANY_TO_ONE:
					
					break;
				case MANY_TO_MANY:
					setManyToMany(field, index, mapping.value());
					break;
				default:
					
				}
			}
		}
	}
	
	private void setManyToMany(Field field, int index, String[] param){
		String fieldName = field.getName();
		MapClass innerClass = new MapClass(param[0], param[1], param[2]);
		mapClassMap.put(fieldName, innerClass);
		
		fieldAsClassMap.put(fieldName, KingUtils.getEntryType(field));
		
		Set<String> searches = new HashSet<>();
		AnalysisClass otherAnalysisClass = Domain.getAnalysisClass(KingUtils.getEntryType(field));
		for(Map.Entry<String, String> entry : otherAnalysisClass.getSelfSearchExpMap().entrySet()){
			String value = String.valueOf(entry.getValue());
			value = value.replace("t0", "t"+(index+1));
			otherSearchExpMap.put(entry.getKey(), value);
			searches.add(entry.getKey());
		}
		
		indexForOtherSearchList.put(index, searches);
		fieldIndexMap.put(fieldName, index);
	}
	
	private void setTableName(Class<?> clazz){
		if(clazz.isAnnotationPresent(TableName.class)){
			tableName = clazz.getDeclaredAnnotation(TableName.class).value();
		}else{
			tableName = clazz.getSimpleName().toLowerCase();
		}
	}
	
	public void putStatistics(Field field, String fieldName){
		if(field.isAnnotationPresent(Statistic.class)){
			Statistic s = field.getDeclaredAnnotation(Statistic.class);
			String key = s.key();
			boolean handleNull = s.handleNull();
			switch(s.statistic()){
			case COUNT:
				statisticsMap.put(key, handleNull ? "COUNT(IFNULL(" + fieldName + ",0))" : "COUNT(" + fieldName + ")");
				break;
			case SUM:
				statisticsMap.put(key, handleNull ? "SUM(IFNULL(" + fieldName + ",0))" : "SUM(=" + fieldName + ")");
				break;
			case AVG:
				statisticsMap.put(key, handleNull ? "AVG(IFNULL(" + fieldName + ",0))" : "AVG(=" + fieldName + ")");
				break;
			case MAX:
				statisticsMap.put(key, handleNull ? "MAX(IFNULL(" + fieldName + ",0))" : "MAX(=" + fieldName + ")");
				break;
			case MIN:
				statisticsMap.put(key, handleNull ? "MIN(IFNULL(" + fieldName + ",0))" : "MIN(=" + fieldName + ")");
				break;
			default:
				System.out.println("异常数据!");
			}
		}
	}
	
	public void setPrimaryKey(Field field, String fieldName){
		if(field.isAnnotationPresent(PrimaryKey.class)){
			primaryKey = fieldName;
			tablePrimaryKey = field.getDeclaredAnnotation(PrimaryKey.class).value();
			if("".equals(tablePrimaryKey)){
				tablePrimaryKey = primaryKey;
			}
			fieldColumnMap.put(primaryKey, tablePrimaryKey);
			columnFieldMap.put(tablePrimaryKey, primaryKey);
		}
	}
	
	public void putColumnAsField(Field field, String fieldName){
		if(field.isAnnotationPresent(ColumnName.class)){
			String columnName = field.getDeclaredAnnotation(ColumnName.class).value();
			fieldColumnMap.put(fieldName, columnName);
			columnFieldMap.put(columnName, fieldName);
		}else if(!field.isAnnotationPresent(Mapping.class)){
			fieldColumnMap.put(fieldName, fieldName);
			columnFieldMap.put(fieldName, fieldName);
		}
	}

	public String getTableName() {
		return tableName;
	}
	
	public String getPrimaryKey() {
		return primaryKey;
	}
	
	public String getTablePrimaryKey(){
		return tablePrimaryKey;
	}
	
	public Map<String, String> getFieldColumnMap() {
		return fieldColumnMap;
	}
	
	public Map<String, String> getNoRepeatMap() {
		return noRepeatMap;
	}
	
	public Set<String> getModifiableFieldSet() {
		return modifiableField;
	}
	
	public Map<String, String> getNoDeletionMap(){
		return noDeletionMap;
	}
	
	public Map<String, String> getSelfSearchExpMap(){
		return selfSearchExpMap;
	}
	
	public Map<String, String> getColumnFieldMap() {
		return columnFieldMap;
	}
	
	public String getExp(String param){
		String exp = selfSearchExpMap.get(param);
		return exp == null ? otherSearchExpMap.get(param) : exp;
	}
	
	public String getColumnName(String fieldName){
		return fieldColumnMap.get(fieldName);
	}
	
	public String getFieldName(String columnName){
		return columnFieldMap.get(columnName);
	}
	
	public String getMapTableName(String fieldName){
		return mapClassMap.get(fieldName).getTableName();
	}
	
	public Map<String, MapClass> getMapClassMap(){
		return mapClassMap;
	}
	
	public String getSelfField(String fieldName){
		return mapClassMap.get(fieldName).selfField();
	}
	
	public String getOtherField(String fieldName){
		return mapClassMap.get(fieldName).otherField();
	}
	
	public Map<String, String> getStatisticsMap(){
		return statisticsMap;
	}
	
	public Map<Integer, Set<String>> getIndexSearch(){
		return indexForOtherSearchList;
	}
	
	public Class<?> getMapClass(String key){
		return fieldAsClassMap.get(key);
	}
	
	public int getTableIndex(String key){
		return fieldIndexMap.get(key);
	}
}

类的逻辑,也很简单,就是给一个类文件,然后遍历类的字段,获取每个字段上的注解,然后保存相应的信息。后面提供了一些getter方法,用来获取这些信息。现在这个解析类,是实现了多对多的查询的,所以逻辑比较复杂一点。但是一对一、一对多、多对一的还没有实现,所以有几个case是空的,另外,columnFieldMap,这个字段,会优化掉,可以不要这个map。

接着分析,一个类,在程序运行期间,一般是不会改变的。当然,现在有种技术,叫热更新,这个是可以在运行时,改变类的文件的。热更新,有好处,但也有不好的地方,应用的场景一般只在测试环境。所以这个暂时可以不考虑,要解决这个问题,也不是很难,可以到后期再做。基于此,就可以设计一个单例模式,一个类,就只有一个解析类,而不是每次要用的时候,都去new一个,那样就太耗性能了。于是:

package org.kingeasy.base;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Domain {

	private static Domain domain = new Domain();
	private final Map<Class<?>, AnalysisClass> classMap = new HashMap<Class<?>, AnalysisClass>();
	private Map<String, Class<?>> roadAsClass;
	private Map<String, String> methodMap;
	private Service service;
	private ObjectMapper mapper;
	
	private Domain(){
		service = new Service();
		mapper = new ObjectMapper();
	}
	
	public static Service getService(){
		return domain.service;
	}
	
	public static ObjectMapper getMapper(){
		return domain.mapper;
	}
	
	public static AnalysisClass getAnalysisClass(Class<?> clazz){
		if(clazz == null){
			return null;
		}
		
		AnalysisClass aClass = domain.classMap.get(clazz);
		if(aClass == null){
			synchronized(clazz){
				aClass = domain.classMap.get(clazz);
				if(aClass == null){
					aClass = new AnalysisClass(clazz);
					domain.classMap.put(clazz, aClass);
				}
				aClass.setOtherSearch(clazz);
			}
		}
		
		return aClass;
	}
	
	public static void setRoadAsClass(Map<String, Class<?>> roadAsClass){
		domain.roadAsClass = roadAsClass;
	}
	
	public static Class<?> getRoadClass(String entryKey){
		return domain.roadAsClass.get(entryKey);
	}
	
	public static String getMethodName(String methodKey){
		return domain.methodMap.get(methodKey);
	}
	
	public static void setMethodMap(Map<String, String> methodMap){
		domain.methodMap = methodMap;
	}
}

Domain是一个单例类,可以把它看作是一个容器,这个容器是唯一的。容器里可以放很多东西,然后这里面就有一个classMap,键就是类文件,值就是类的解析类。Domain提供一个方法getAnalysisClass,参数是类文件,然后就会到classMap里去取类的解析类。如果classMap中有,就直接取出来返回给调用者。如果classMap中没有,就会New一个,然后把New出来的,放入classMap中,同时返回给方法的调用者。现在这个Domain里面,还放了一些其它的东西,而其它的东西,还没有讲到,等之后,会慢慢地讲到,不过,只是贴代码,还是比较难理解设计的逻辑的。就现在来讲,最难理解的逻辑,应该就是多对多查询的实现。这个,也只能等讲到通用查询方法实现的时候,再细说了。

欢迎加入测试群:91425817,一起讨论测试的那此事。

发布了47 篇原创文章 · 获赞 9 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/kingzhsh/article/details/98477362