Mybatis的Reflector解析

本文深入分析了 MyBatis 中 Reflector 类的构造过程,包括默认构造函数初始化、get 和 set 方法映射、字段处理等关键步骤,并详细解释了如何解决方法冲突。
Mybatis的MetaObject解析:[url]http://donald-draper.iteye.com/admin/blogs/2338818[/url]
在上篇分析MetaObejct时候,MetaObejct的方法的实现是依赖ObjectWrapper,ObjectWrapper依赖于MetaClass,而MetaClass依赖于Reflector,这一节我们就来看看Reflector
//Reflector
public class Reflector
{
private static boolean classCacheEnabled = true;
private static final String EMPTY_STRING_ARRAY[] = new String[0];
private static final Map REFLECTOR_MAP = new ConcurrentHashMap();
private Class type;
private String readablePropertyNames[];//可读属性名
private String writeablePropertyNames[];//可写属性名
private Map setMethods;Map<String,MethodInvoker>
private Map getMethods;Map<String,MethodInvoker>
private Map setTypes;Map<String,Class>
private Map getTypes;Map<String,Class>
private Constructor defaultConstructor;//默认构造函数
private Map caseInsensitivePropertyMap;//大小写不敏感属性Map
//构造Class对应的Reflector
public static Reflector forClass(Class clazz)
{
if(classCacheEnabled)
{
//如果缓存状态开启,则从REFLECTOR_MAP获取对应的Reflector,没有则重新构建
Reflector cached = (Reflector)REFLECTOR_MAP.get(clazz);
if(cached == null)
{
cached = new Reflector(clazz);
REFLECTOR_MAP.put(clazz, cached);
}
return cached;
} else
{
return new Reflector(clazz);
}
}
private Reflector(Class clazz)
{
readablePropertyNames = EMPTY_STRING_ARRAY;//可读属性名
writeablePropertyNames = EMPTY_STRING_ARRAY;//可写属性名
setMethods = new HashMap();//Set方法Map
getMethods = new HashMap();//Get方法Map
setTypes = new HashMap();
getTypes = new HashMap();
caseInsensitivePropertyMap = new HashMap();//大小写不敏感属性Map
type = clazz;
addDefaultConstructor(clazz);
addGetMethods(clazz);
addSetMethods(clazz);
addFields(clazz);
readablePropertyNames = (String[])getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
writeablePropertyNames = (String[])setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
//将可读属性添加大小写不敏感属性Map
String arr$[] = readablePropertyNames;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; i$++)
{
String propName = arr$[i$];
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
//将可写属性添加大小写不敏感属性Map
arr$ = writeablePropertyNames;
len$ = arr$.length;
for(int i$ = 0; i$ < len$; i$++)
{
String propName = arr$[i$];
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}

}
}

来看addDefaultConstructor
初始化默认构造函数为,无参构造函数
private void addDefaultConstructor(Class clazz)
{
Constructor consts[] = clazz.getDeclaredConstructors();
Constructor arr$[] = consts;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; i$++)
{
Constructor constructor = arr$[i$];
if(constructor.getParameterTypes().length != 0)
continue;
if(canAccessPrivateMethods())
try
{
constructor.setAccessible(true);
}
catch(Exception e) { }
if(constructor.isAccessible())
defaultConstructor = constructor;
}

}

来看addGetMethods
将Class的Get方法添加到
 private void addGetMethods(Class cls)
{
Map conflictingGetters = new HashMap();
Method methods[] = getClassMethods(cls);
Method arr$[] = methods;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; i$++)
{
Method method = arr$[i$];
String name = method.getName();
if(name.startsWith("get") && name.length() > 3)
{
if(method.getParameterTypes().length == 0)
{
name = PropertyNamer.methodToProperty(name);
//将name对应的get方法添加到conflictingGetters-Map中
addMethodConflict(conflictingGetters, name, method);
}
continue;
}
if(name.startsWith("is") && name.length() > 2 && method.getParameterTypes().length == 0)
{
name = PropertyNamer.methodToProperty(name);
addMethodConflict(conflictingGetters, name, method);
}
}
//解决方法冲突
resolveGetterConflicts(conflictingGetters);
}
private void addMethodConflict(Map conflictingMethods, String name, Method method)
{
List list = (List)conflictingMethods.get(name);
if(list == null)
{
list = new ArrayList();
conflictingMethods.put(name, list);
}
list.add(method);
}

private void resolveGetterConflicts(Map conflictingGetters)
{
for(Iterator i$ = conflictingGetters.keySet().iterator(); i$.hasNext();)
{
String propName = (String)i$.next();
List getters = (List)conflictingGetters.get(propName);
Iterator iterator = getters.iterator();
Method firstMethod = (Method)iterator.next();
if(getters.size() == 1)
{
addGetMethod(propName, firstMethod);
} else
{
Method getter = firstMethod;
Class getterType = firstMethod.getReturnType();
do
{
if(!iterator.hasNext())
break;
Method method = (Method)iterator.next();
Class methodType = method.getReturnType();
if(methodType.equals(getterType))
throw new ReflectionException((new StringBuilder()).append("Illegal overloaded getter method with ambiguous type for property ").append(propName).append(" in class ").append(firstMethod.getDeclaringClass()).append(". This breaks the JavaBeans ").append("specification and can cause unpredicatble results.").toString());
if(!methodType.isAssignableFrom(getterType))
if(getterType.isAssignableFrom(methodType))
{
getter = method;
getterType = methodType;
} else
{
throw new ReflectionException((new StringBuilder()).append("Illegal overloaded getter method with ambiguous type for property ").append(propName).append(" in class ").append(firstMethod.getDeclaringClass()).append(". This breaks the JavaBeans ").append("specification and can cause unpredicatble results.").toString());
}
} while(true);
//将name对应的get方法添加到getMethods集合中
addGetMethod(propName, getter);
}
}

}
//将name对应的get方法添加到getMethods集合中
private void addGetMethod(String name, Method method)
{
if(isValidPropertyName(name))
{
getMethods.put(name, new MethodInvoker(method));
getTypes.put(name, method.getReturnType());
}
}

来看addSetMethods
private void addSetMethods(Class cls)
{
Map conflictingSetters = new HashMap();
Method methods[] = getClassMethods(cls);
Method arr$[] = methods;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; i$++)
{
Method method = arr$[i$];
String name = method.getName();
if(name.startsWith("set") && name.length() > 3 && method.getParameterTypes().length == 1)
{
name = PropertyNamer.methodToProperty(name);
//将name对应的set方法添加到conflictingSetters-Map中
addMethodConflict(conflictingSetters, name, method);
}
}
//解决方法冲突
resolveSetterConflicts(conflictingSetters);
}

private void addMethodConflict(Map conflictingMethods, String name, Method method)
{
List list = (List)conflictingMethods.get(name);
if(list == null)
{
list = new ArrayList();
conflictingMethods.put(name, list);
}
list.add(method);
}

private void resolveSetterConflicts(Map conflictingSetters)
{
Iterator i$ = conflictingSetters.keySet().iterator();
do
{
if(!i$.hasNext())
break;
String propName = (String)i$.next();
List setters = (List)conflictingSetters.get(propName);
Method firstMethod = (Method)setters.get(0);
if(setters.size() == 1)
{
addSetMethod(propName, firstMethod);
continue;
}
Class expectedType = (Class)getTypes.get(propName);
if(expectedType == null)
throw new ReflectionException((new StringBuilder()).append("Illegal overloaded setter method with ambiguous type for property ").append(propName).append(" in class ").append(firstMethod.getDeclaringClass()).append(". This breaks the JavaBeans ").append("specification and can cause unpredicatble results.").toString());
Iterator methods = setters.iterator();
Method setter = null;
do
{
if(!methods.hasNext())
break;
Method method = (Method)methods.next();
if(method.getParameterTypes().length != 1 || !expectedType.equals(method.getParameterTypes()[0]))
continue;
setter = method;
break;
} while(true);
if(setter == null)
throw new ReflectionException((new StringBuilder()).append("Illegal overloaded setter method with ambiguous type for property ").append(propName).append(" in class ").append(firstMethod.getDeclaringClass()).append(". This breaks the JavaBeans ").append("specification and can cause unpredicatble results.").toString());
//将对应的set方法添加到setMethods-map中
addSetMethod(propName, setter);
} while(true);
}

private void addSetMethod(String name, Method method)
{
if(isValidPropertyName(name))
{
setMethods.put(name, new MethodInvoker(method));
setTypes.put(name, method.getParameterTypes()[0]);
}
}

来看addFields
private void addFields(Class clazz)
{
Field fields[] = clazz.getDeclaredFields();
Field arr$[] = fields;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; i$++)
{
Field field = arr$[i$];
if(canAccessPrivateMethods())
try
{
field.setAccessible(true);
}
catch(Exception e) { }
if(!field.isAccessible())
continue;
if(!setMethods.containsKey(field.getName()))
addSetField(field);
if(!getMethods.containsKey(field.getName()))
addGetField(field);
}

if(clazz.getSuperclass() != null)
addFields(clazz.getSuperclass());
}
//将Field的对应set方法,及参数类型添加到setMethods,setTypes-Map中
private void addSetField(Field field)
{
if(isValidPropertyName(field.getName()))
{
setMethods.put(field.getName(), new SetFieldInvoker(field));
setTypes.put(field.getName(), field.getType());
}
}
//将Field的对应get方法,及参数类型添加到setMethods,setTypes-Map中
private void addGetField(Field field)
{
if(isValidPropertyName(field.getName()))
{
getMethods.put(field.getName(), new GetFieldInvoker(field));
getTypes.put(field.getName(), field.getType());
}
}

总结:
[color=blue]从以上分析,Reflector构造函数,所做的工作为,初始构造函数,将Class的set与get方法及参数添加到setMethods,getMethods,setTypes,getTypes对应的Map中,setMethods,getMethods Map的key属性名,value为对应的方法,然后初始化可读,可写,及大小写不明感属性集合。[/color]
<think>我们正在讨论MyBatis的反射底层原理。根据之前的引用,我们知道MyBatis在底层使用了反射机制,特别是在Mapper接口与XML映射文件的绑定过程中。现在,我们需要深入探讨MyBatis如何利用反射来实现这些功能。 关键点: 1. MyBatis通过动态代理创建Mapper接口的代理对象。 2. 当调用Mapper接口的方法时,实际上调用的是代理对象的方法,代理对象会拦截方法调用,并根据方法名和参数去执行相应的SQL。 3. 反射在MyBatis中的应用主要包括: - 通过反射获取Mapper接口的方法信息(方法名、参数类型、返回类型等)。 - 利用反射调用方法(在结果映射时,将查询结果通过反射设置到实体对象的属性中)。 根据引用[3]:MyBatis底层通过反射机制完成了mapper接口方法和xml映射文件标签的绑定。也就是说,每一个Mapper接口中的方法都对应MappedStatement对象(存储在Configuration中),而MappedStatement包含了SQL语句、参数映射、结果映射等信息。 具体步骤: 1. 在初始化阶段,MyBatis解析所有的Mapper.xml文件,将每个SQL语句封装成MappedStatement对象,并以一个唯一的ID(通常是接口全限定名+方法名)存储到Configuration对象中。 2. 当获取Mapper接口的代理对象时,使用动态代理(JDK动态代理)生成代理实例。这个代理实例由MapperProxy类处理。 3. 当调用Mapper接口方法时,代理对象会拦截调用,并交给MapperProxy的invoke方法处理。 4. MapperProxy根据调用的方法信息(方法名和参数)构建一个MapperMethod对象(如果缓存中没有的话),然后执行MapperMethod的execute方法。 5. 在MapperMethod中,根据方法返回类型(由反射获取)决定执行SQL的方式(selectOne, selectList, insert, update等)。 6. 执行SQL后,利用反射将结果集映射到Java对象(通过反射创建对象,并设置属性值)。 反射在结果映射中的使用: - 结果集映射时,MyBatis通过反射创建实体类的实例(调用无参构造器)。 - 然后通过反射调用实体类的setter方法(或直接设置字段,如果配置为使用字段而不是setter)来设置属性值。 另外,参数传递时,反射用于获取方法的参数名(如果使用注解@Param)或参数类型,以便将参数设置到SQL语句中。 因此,我们可以将MyBatis反射底层原理分为两个主要部分: 1. 接口方法的代理调用(动态代理+反射) 2. 结果集到Java对象的映射(反射) 下面我们具体展开: 一、动态代理创建Mapper接口的代理对象 MyBatis使用JDK动态代理为Mapper接口创建代理对象。核心类是MapperProxyFactory和MapperProxy。 二、反射在方法调用和结果映射中的应用 - 方法调用:通过反射获取方法签名(方法名、参数类型、返回类型)来定位对应的SQL语句。 - 结果映射:通过反射创建对象并设置属性。 三、反射在参数处理中的应用 - 将Java方法的参数转换为SQL语句的参数。 代码示例(模拟MyBatis反射使用): 由于MyBatis源码较复杂,我们简化模拟: 步骤1:创建代理对象 ```java public class MapperProxyFactory<T> { private final Class<T> mapperInterface; public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface); return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); } } ``` 步骤2:实现InvocationHandler(MapperProxy) ```java public class MapperProxy<T> implements InvocationHandler { private final SqlSession sqlSession; private final Class<T> mapperInterface; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 如果方法是Object类的方法,直接调用(如toString, hashCode等) if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } // 根据方法名和接口类型,定位到要执行的SQL(在MyBatis中是通过MapperMethod来执行的) // 这里简化:直接调用SqlSession的方法 // 实际上,MyBatis会使用MapperMethod来统一处理 // 我们假设方法名就是SQL语句的id(全限定名+方法名) String statement = mapperInterface.getName() + "." + method.getName(); // 根据返回类型选择执行的方法 Class<?> returnType = method.getReturnType(); if (List.class.isAssignableFrom(returnType)) { return sqlSession.selectList(statement, args == null ? null : args[0]); } else { return sqlSession.selectOne(statement, args == null ? null : args[0]); } } } ``` 步骤3:结果映射(反射创建对象和设置属性) 在sqlSession.selectOne方法内部,会执行SQL,然后使用ResultSetHandler处理结果集。在ResultSetHandler中,使用反射创建对象并设置属性。 示例代码(简化): ```java public class DefaultResultSetHandler { public <E> List<E> handleResultSets(Statement stmt) throws SQLException { List<E> results = new ArrayList<>(); ResultSet rs = stmt.getResultSet(); while (rs.next()) { // 通过反射创建实体类实例(假设我们已知实体类类型) E entity = (E) entityClass.newInstance(); // 遍历结果集的列 ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); for (int i = 1; i <= columnCount; i++) { String columnName = metaData.getColumnName(i); Object value = rs.getObject(i); // 通过反射设置属性(这里简单使用字段,实际MyBatis可以配置setter方法) Field field = entityClass.getDeclaredField(columnName); field.setAccessible(true); field.set(entity, value); } results.add(entity); } return results; } } ``` 注意:实际MyBatis的结果映射更复杂,支持构造函数映射、嵌套映射等,但基本原理都是反射。 四、反射获取方法参数名 在Java8之前,无法通过反射直接获取方法参数名(会得到arg0, arg1这样的名字)。MyBatis提供了@Param注解来指定参数名。在Java8之后,可以通过编译时加入参数表(-parameters)来获取真实参数名。MyBatis中通过ParamNameResolver来解析参数名。 总结: MyBatis的反射底层原理主要体现在: 1. 动态代理Mapper接口,拦截方法调用。 2. 利用反射获取方法信息(方法名、参数类型、返回类型)来定位SQL。 3. 通过反射创建结果对象并设置属性值。 4. 通过反射解析方法参数名,将参数传递给SQL。 因此,反射是MyBatis实现ORM映射和动态代理调用的核心技术之一。 引用说明: [^3] 说明了MyBatis通过反射完成接口方法和XML映射的绑定。 [^4] 提到了JDBC的基本操作,而MyBatis底层基于JDBC,反射用于将结果集映射到对象。 相关问题: 1. MyBatis中如何解决反射设置属性时的性能问题? 2. MyBatis除了反射还使用了哪些技术来实现ORM映射? 3. MyBatis的Mapper接口没有实现类,它是如何被实例化的? 4. MyBatis在结果映射时如何处理复杂的嵌套关系? </think> ### MyBatis反射底层原理解析 MyBatis的反射底层原理主要涉及**动态代理生成Mapper接口实现**和**反射实现对象映射**两大核心机制,以下是详细解析: #### 一、Mapper接口动态代理原理 1. **代理对象生成** MyBatis通过`MapperProxyFactory`创建Mapper接口的代理对象(JDK动态代理): ```java public class MapperProxyFactory<T> { public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface); return (T) Proxy.newProxyInstance( mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy // 调用处理器 ); } } ``` 2. **方法调用拦截** 代理对象的方法调用被`MapperProxy.invoke()`拦截处理: ```java public class MapperProxy<T> implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 获取MapperMethod(缓存中获取或新建) MapperMethod mapperMethod = cachedMapperMethod(method); // 2. 执行SQL并返回结果 return mapperMethod.execute(sqlSession, args); } } ``` 3. **SQL执行映射** `MapperMethod`将接口方法映射为具体SQL操作: ```java public class MapperMethod { public Object execute(SqlSession sqlSession, Object[] args) { switch (command.getType()) { case SELECT: return sqlSession.selectOne(command.getName(), param); case INSERT: return sqlSession.insert(command.getName(), param); // UPDATE/DELETE 同理 } } } ``` [^3] #### 二、反射实现对象映射 1. **结果集映射** 通过反射将`ResultSet`转换为Java对象: ```java public class DefaultResultSetHandler { private Object createResultObject(ResultSet rs, Class<?> resultType) { Object resultObject = resultType.newInstance(); // 反射创建对象 ResultSetMetaData metaData = rs.getMetaData(); for (int i = 1; i <= metaData.getColumnCount(); i++) { String column = metaData.getColumnName(i); Field field = resultType.getDeclaredField(column); field.setAccessible(true); field.set(resultObject, rs.getObject(column)); // 反射设置属性值 } return resultObject; } } ``` [^4] 2. **参数映射** 方法参数通过`ParamNameResolver`解析为SQL参数: ```java public class ParamNameResolver { public Object getNamedParams(Object[] args) { // 反射获取参数名(Java8+的Parameter或@Param注解) String[] paramNames = getParamNames(method); Map<String, Object> paramMap = new HashMap<>(); for (int i = 0; i < args.length; i++) { paramMap.put(paramNames[i], args[i]); } return paramMap; } } ``` #### 三、核心反射组件 | 组件 | 作用 | 反射应用点 | |---------------------|-----------------------------------|------------------------------| | `MapperProxy` | 动态代理处理器 | 拦截接口方法调用 | | `MapperMethod` | 方法到SQL的映射 | 解析方法签名和返回类型 | | `ResultSetHandler` | 结果集处理 | 反射创建对象+属性赋值 | | `ParamNameResolver` | 参数名解析 | 获取方法参数名(反射/注解) | | `Reflector` | MyBatis自带的反射工具类 | 缓存类元数据提升性能 | #### 四、性能优化策略 1. **元数据缓存** `Reflector`类缓存Class的字段/方法信息,避免重复反射: ```java public class Reflector { private static final Map<Class<?>, Reflector> REFLECTOR_MAP = new ConcurrentHashMap<>(); public Reflector(Class<?> clazz) { // 缓存字段/getter/setter等元数据 } } ``` 2. **字节码增强** 使用`javassist`生成`get/set`方法的字节码,替代反射调用(可选优化)。 3. **类型处理器** 内置`TypeHandler`处理特定类型的转换(如Date→Timestamp),减少反射调用[^2]。 #### 五、执行流程示例 1. 调用`userMapper.getUserById(1)` 2. `MapperProxy`拦截方法调用 3. `MapperMethod`解析为`SELECT`操作 4. 执行SQL:`SELECT * FROM user WHERE id = ?` 5. `ResultSetHandler`通过反射创建User对象 6. 遍历ResultSet,反射设置`user.setId()`/`user.setName()` 7. 返回User对象 > **数学表达**:设$M$为Mapper接口,$m$为接口方法,$P$为参数,则代理执行过程可表示为: > $$ \text{ProxyCall}(M.m(P)) = \text{SqlSession} \cdot \text{execute}( \text{Map}(m), \text{Resolve}(P) ) $$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值