
作者: 西魏陶淵明
部落格: https://blog.springlearn.cn/
西魏陶淵明
莫笑少年江湖夢,誰不少年夢江湖
核心類介紹
前面我們知道Mybatis的解析原理,知道了在
、
Configuration
MapperBuilderAssistant
出現了很多核心的類。
正是由這些類來實作了,Mybatis的核心功能。是以要想完全搞懂 Mybatis,這些類就必須要進行深入的研究,廢話不多少,直接就開始吧。
其實這裡面的每個類要都能單獨拆出來一篇進行詳細說明,但是這裡我們隻取其精華,知道他的作用,及如何使用。和能借鑒的地方就可以了。
一、Configuration
屬性 | 解釋 |
TypeAliasRegistry | key是一個别名,value是一個class對象 |
Properties variables | 配置檔案中占位符的變量配置 |
InterceptorChain interceptorChain | 攔截鍊,用于攔截方法,實作插件 |
ObjectFactory objectFactory | 對象執行個體化統一的工廠方法 |
ObjectWrapperFactory objectWrapperFactory | 擴充使用,允許使用者自定義包裝對象ObjectWrapper |
ReflectorFactory reflectorFactory | 反射工廠,用于生成一個反射資訊對象 |
Environment environment | 環境資訊包含(事務管理器和資料源) |
TypeHandlerRegistry typeHandlerRegistry | 資料庫傳回資料類型轉換成Java對象的處理器,或是Java資料類型轉換jdbc資料類型的處理器 |
MapperRegistry mapperRegistry | Mapper生成的處理類,包含代理的邏輯 |
1.1 TypeAliasRegistry
key是别名,value是對應的Class<?>
這個在什麼時候用的呢? 前面我們通過解析xml,發現很多的dtd限制,檔案的值類型都是 CDATA 即 字元串。 但是這些字元串最終是要解析成指定的位元組碼的。
怎麼知道字元串對應的是哪個java類呢? 那麼這個功能就交給
TypeAliasRegistry
。允許你将一個java類注冊一個别名。這樣你就可以在配置檔案中用别名
來替換java類了。
@Test
public void TypeAliasRegistry() {
TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
System.out.println(typeAliasRegistry.resolveAlias("byte"));
}
1.2 Properties
這個java類就不用介紹了,在
Configuration
就是存儲的配置資訊,允許你在mybatis中任意地方使用${}進行通路資料。
比如你可以這樣用? 配置一個全局的limit限制數量
datasource.driver-class-name=com.mysql.cj.jdbc.Driver
datasource.url=jdbc:mysql://127.0.0.1:3306/test
datasource.username=root
datasource.password=123456
datasource.globalLimit=1000
public interface TUserMapper {
@Select("select * from t_user where uid = ${id} limit ${datasource.globalLimit} ")
List<TUser> selectById(Long id);
}
1.3 InterceptorChain
内容較多,開單獨的篇幅進行介紹; 第07篇:Mybatis的插件設計分析
從名字就可以看到是一個攔截鍊; 主要是實作插件的功能。核心思路是, 通過攔截類的方法來實作插件。
MyBatis 允許你在映射語句執行過程中的某一點進行攔截調用。預設情況下,MyBatis 允許使用插件來攔截的方法調用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
1.4 ObjectFactory 對象工廠
在Mybatis中或者說是orm架構中, 使用到反射的地方較多。那麼就一定會遇到執行個體化的問題。具體如何執行個體化。就是使用對象工廠。
之是以提供個工廠, 小編個人認為還是為了擴充使用。但是實際中一般不會擴充這個類。因為該有的功能預設的就已經具備了。
public interface ObjectFactory {
// 配置資訊
default void setProperties(Properties properties) {}
// 根據空構造來執行個體化
<T> T create(Class<T> type);
// 根據構造參數來執行個體化
<T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
// 判斷是否是Collection子類
<T> boolean isCollection(Class<T> type);
}
1.5 ObjectWrapperFactory 對象包裝工廠
他的作用主要是提供外面的擴充,允許使用者自己去建立包裝對象。實際架構中不會用到這個對象。我們隻要知道他的作用是什麼行。
我們重點說一下 ObjectWrapper 。
ObjectWrapper的主要作用是,提供統一的屬性操作方法。主要在MetaObject被使用,如下。
public class MetaObject {
private final Object originalObject;
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}
}
我們看到普通的對象,被包裝成 ObjectWrapper後就可以使用通用的API來擷取和修改對象數值型,以及可以擷取屬性值的類型資訊,如下面的例子。
@Test
public void objectWrapper(){
// 讀取配置資訊(為什麼路徑前不用加/,因為是相對路徑。maven編譯後的資源檔案和class檔案都是在一個包下,是以不用加/就是目前包目錄)
InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
// 生成SqlSession工廠,SqlSession從名字上看就是,跟資料庫互動的會話資訊,負責将sql送出到資料庫進行執行
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development");
// 擷取Mybatis配置資訊
Configuration configuration = sqlSessionFactory.getConfiguration();
Map<String,String> map = new HashMap<>();
map.put("name","孫悟空");
MetaObject metaObject = MetaObject.forObject(map, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
System.out.println(metaObject.getValue("name"));
// 複制
metaObject.setValue("age",18);
// {name=孫悟空, age=18}
System.out.println(map);
TUser tUser = new TUser();
tUser.setName("唐三藏");
MetaObject tUserMetaObject = MetaObject.forObject(tUser, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
// 唐三藏
System.out.println(tUserMetaObject.getValue("name"));
List<TUser> users = new ArrayList<>();
users.add(tUser);
MetaObject tUserMetaObjects = MetaObject.forObject(users, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
tUserMetaObjects.add(new TUser());
// [TUser(tokenId=null, uid=null, name=唐三藏), TUser(tokenId=null, uid=null, name=null)]
System.out.println(tUserMetaObjects.getOriginalObject());
}
1.6 ReflectorFactory 反射工廠
從名字看就是反射的工廠,主要是為了生成 Reflector 對象。Reflector 對反射的資訊進行了緩存。用的時候直接從緩存中擷取。
public interface ReflectorFactory {
boolean isClassCacheEnabled();
void setClassCacheEnabled(boolean classCacheEnabled);
Reflector findForClass(Class<?> type);
}
1.7 Environment 環境
這裡面的環境屬性,是比較重要。因為他直接決定了你要跟那個資料庫互動。以及事務如何處理。
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
1.8 TypeHandlerRegistry
TypeHandler + Registry, 從名字來看又是一個類型注冊器用于反射使用。看來mybatis中用于反射的工具類是在太多了。那麼TypeHandler究竟有什麼用呢?
TypeHandler 是對Statement和ResultSet負責。
ResultSet 是從資料庫擷取的資料的載體,Statement 是準備向資料庫送出資料的載體。TypeHandler 的作用就是
根據資料類型, 處理跟資料的輸入和輸出資訊。看下面接口。
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* Gets the result.
*
* @param rs
* the rs
* @param columnName
* Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
* @return the result
* @throws SQLException
* the SQL exception
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
這裡舉一個例子,比如name這個字段在資料庫是varchar類型,但是java對象中name是一個Name對象。那麼如何處理呢?
我們自定義一個處理器。
public class NameTypeHandler implements TypeHandler<Name> {
@Override
public void setParameter(PreparedStatement ps, int i, Name parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.getFirstName() + "-" + parameter.getSurname());
}
@Override
public Name getResult(ResultSet rs, String columnName) throws SQLException {
String name = rs.getString(columnName);
String[] split = name.split("-");
return new Name(split[0], split[1]);
}
}
然後在配置檔案中聲明注冊器,用于将java對象轉換成jdbc資料庫字段類型。同時也将資料庫查詢到的jdbc類型轉換成java對象。
<configuration>
<typeHandlers>
<typeHandler handler="orm.example.dal.type.NameTypeHandler" javaType="orm.example.dal.model.Name"></typeHandler>
</typeHandlers>
</configuration>
<mapper>
<insert id="insert" parameterType="orm.example.dal.model.T2User">
<!--
WARNING - @mbggenerated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sun Mar 27 23:01:23 CST 2022.
-->
insert into T_USER (token_id, uid, name)
values (#{tokenId,jdbcType=CHAR}, #{uid,jdbcType=INTEGER}, #{name,javaType=orm.example.dal.model.Name })
</insert>
</mapper>
我們執行下面代碼,可以看到我們将資料類型轉換成了jdbc存到了資料庫,同時執行查詢時候又将jdbc類型轉換成了java對象。這就是它的作用。
@Test
public void test() {
// 讀取配置資訊(為什麼路徑前不用加/,因為是相對路徑。maven編譯後的資源檔案和class檔案都是在一個包下,是以不用加/就是目前包目錄)
InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
// 生成SqlSession工廠,SqlSession從名字上看就是,跟資料庫互動的會話資訊,負責将sql送出到資料庫進行執行
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development");
// 擷取Mybatis配置資訊
Configuration configuration = sqlSessionFactory.getConfiguration();
configuration.getTypeHandlerRegistry().register(new NameTypeHandler());
// 參數: autoCommit,從名字上看就是是否自動送出事務
SqlSession sqlSession = sqlSessionFactory.openSession(false);
// 擷取Mapper
T2UserMapper mapper = configuration.getMapperRegistry().getMapper(T2UserMapper.class, sqlSession);
T2User tUser = new T2User();
Name name = new Name("孫","悟空");
tUser.setName(name);
tUser.setTokenId("西天取經");
mapper.insert(tUser);
// 擷取插入的資料: T2User(tokenId=西天取經, uid=32, name=Name(surname=悟空, firstName=孫))
System.out.println(mapper.selectByPrimaryKey("西天取經"));
// 資料插入後,執行查詢,然後復原資料
sqlSession.rollback();
}
1.9 MapperRegistry
看到Registry又知道了,這貨又是一個類似Map的工具類。肯定是跟Mapper有關系。下面代碼關鍵在于13和17行。
Mybatis中擷取Mapper對象都是從 MapperRegistry中擷取的。
- line(13)
接口生成代理對象new MapperProxyFactory<>(type)
- line(17)
用于解析Mybatis支援的注解,并添加到MapperAnnotationBuilder
Configuration
這兩個類比較重要我們開單獨的篇幅進行說明。
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
1.10 SqlSession
SqlSession相當于一千個橋梁,負責将方法參數,發送給資料庫,并且将資料庫傳回值組裝成方法的傳回值。
在SqlSession中有幾個比較重要的類,如下圖。他們負責不同的邏輯。
分别處理入參(ParameterHandler),處理出參(ResultSetHandler),生成Jdbc(StatementHandler),處理緩存相關(Executor)。
是一個非常重要的一個類。後面我們的學習中會經常看到。
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
@Override
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();
}
我們看增删改查的方法入參無非2個。1個是statement,1個是入參。
其中statement主要是為了擷取 MappedStatement。如下
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
另外一個入參是為了組裝sql資訊。MappedStatement#getBoundSql 擷取sql資訊。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
二、MapperBuilderAssistant
屬性 | 解釋 |
MapperBuilderAssistant | Mapper建構輔助工具類(緩存配置) |
CacheRefResolver | 決定如何使用緩存 |
ParameterMapping | 參數映射類 |
ResultMapResolver | 傳回值映射 |
Map<String, XNode> sqlFragments | sql片段 |
MappedStatement | Mapper方法的所有資訊(出參,入參) |
2.1 MapperBuilderAssistant
Mapper建構工具類,下面小編列舉了幾個方法。可以看出來基本都是用于處理sql結果集向java對象轉換使用,和對Mapper方法簽名分析生成sql的工具。
下面我們一個一個來看看。
public class MapperBuilderAssistant extends BaseBuilder {
// 确定使用那個緩存
public Cache useCacheRef(String namespace);
// 生成2級緩存對象
public Cache useNewCache(...);
// 每個參數的資訊
public ParameterMapping buildParameterMapping();
// 生成結構集
public ResultMap addResultMap();
// 鑒别器
public Discriminator buildDiscriminator();
// 生成Mapper簽名
public MappedStatement addMappedStatement();
// 擷取方言處理器
public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass);
}
2.1.1 Cache
Mybatis 緩存的接口定義,用于緩存查詢sql的結果。Mybatis中一級緩存和二級緩存是一個面試經常會考的問題。這個類我們也單獨開一篇私聊。
2.1.2 ParameterMapping & ResultMapping
從名字中能看到就是對Mapper中方法的入參和出參的映射關系類。
public class ParameterMapping {
private Configuration configuration;
private String property;
private ParameterMode mode;
private Class<?> javaType = Object.class;
private JdbcType jdbcType;
private Integer numericScale;
private TypeHandler<?> typeHandler;
private String resultMapId;
private String jdbcTypeName;
private String expression;
}
如圖所示,會對方法的每個參數,生成一個 ParameterMapping對象。存儲了java類型和db的類型的映射關系。
2.1.3 ResultMap
從名字看就是對jdbc結果集向Mapper傳回值的映射關系,用于将jdbc資料重新映射成Java對象。
@Test
public void resultSet(){
// 讀取配置資訊(為什麼路徑前不用加/,因為是相對路徑。maven編譯後的資源檔案和class檔案都是在一個包下,是以不用加/就是目前包目錄)
InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
// 生成SqlSession工廠,SqlSession從名字上看就是,跟資料庫互動的會話資訊,負責将sql送出到資料庫進行執行
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development");
// 擷取Mybatis配置資訊
Configuration configuration = sqlSessionFactory.getConfiguration();
SqlSession sqlSession = sqlSessionFactory.openSession(false);
TUserMapper mapper = configuration.getMapper(TUserMapper.class,sqlSession);
System.out.println(mapper.selectAll());
MappedStatement mappedStatement = configuration.getMappedStatement("orm.example.dal.mapper.TUserMapper.selectAll");
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
System.out.println(resultMaps);
}
2.1.4 LanguageDriver
主要用于生成 SqlSource,動态sql(XMLLanguageDriver)或者靜态sql(RawLanguageDriver)
public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
動态sql可以處理下面這些标簽
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
2.2 CacheRefResolver
确定每個Mapper配置的緩存
public class CacheRefResolver {
private final MapperBuilderAssistant assistant;
private final String cacheRefNamespace;
public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
this.assistant = assistant;
this.cacheRefNamespace = cacheRefNamespace;
}
public Cache resolveCacheRef() {
return assistant.useCacheRef(cacheRefNamespace);
}
}
2.3 MappedStatement
可以說關于Mapper所有的資訊都在這個類裡面,包括sql資訊、入參及傳回值類型、sql類型(SqlCommandType)、是否使用緩存、
是否重新整理緩存、StatementType類型。
public final class MappedStatement {
// mapper/TUserMapper.xml
private String resource;
// 全局配置
private Configuration configuration;
// orm.example.dal.mapper.TUserMapper.insert
private String id;
//
private Integer fetchSize;
// 逾時時間
private Integer timeout;
// StatementType.PREPARED
private StatementType statementType;
// ResultSetType.DEFAULT(-1),
private ResultSetType resultSetType;
// RawSqlSource
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
// SqlCommandType( UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH)
private SqlCommandType sqlCommandType;
// 生成id
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
}
生成主要有2種方法。
- xml的方式 XMLStatementBuilder
- 通過注解的方式 MapperAnnotationBuilder
public class XMLMapperBuilder extends BaseBuilder
public void parse() {
// 如果有資源檔案先解析xml,并儲存到Configuration#addMappedStatement
if (!configuration.isResourceLoaded(resource)) {
// XMLStatementBuilder進行解析
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 同時使用MapperAnnotationBuilder類解析
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
public class MapperAnnotationBuilder{
// 隻有包含了下面注解的方法才會被解析
private static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream
.of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class,
InsertProvider.class, DeleteProvider.class)
.collect(Collectors.toSet());
public void parseStatement(Method method) {
final Class<?> parameterTypeClass = getParameterType(method);
final LanguageDriver languageDriver = getLanguageDriver(method);
// 判斷是否包含了上面的注解
getAnnotationWrapper(method, true, statementAnnotationTypes)
.ifPresent(statementAnnotation -> {})
}
}
Mapper配置檔案在解析的時候首先,回去解析xml,然後解析注解。如果兩種方式都存在那麼就會提示錯誤。
Caused by: java.lang.IllegalArgumentException: Mapped Statements
collection already contains value for
orm.example.dal.mapper.TUserMapper.selectAll. please check
mapper/TUserMapper.xml and orm/example/dal/mapper/TUserMapper.java
(best guess) at
org.apache.ibatis.session.ConfigurationStrictMap.put(Configuration.java:970)
原因就在 StrictMap。
public V put(String key, V value) {
if (containsKey(key)) {
throw new IllegalArgumentException(name + " already contains value for " + key
+ (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
}
if (key.contains(".")) {
final String shortKey = getShortName(key);
if (super.get(shortKey) == null) {
super.put(shortKey, value);
} else {
super.put(shortKey, (V) new Ambiguity(shortKey));
}
}
return super.put(key, value);
}
三、可以借鑒的知識點
3.1 包裝器模式
ObjectWrapper
@Test
public void objectWrapper() {
TUser mock = JMockData.mock(TUser.class);
MetaObject metaObject = MetaObject.forObject(mock, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
boolean name = metaObject.hasGetter("name");
if (name) {
// iuslA4Xp
System.out.println(metaObject.getValue("name"));
}
Map<String,Object> map = new HashMap<>();
map.put("age",18);
MetaObject metaMap = MetaObject.forObject(map, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
boolean age = metaMap.hasGetter("age");
if (age) {
// 18
System.out.println(metaMap.getValue("age"));
}
}
3.2 MetaClass
@Test
public void metaClass()throws Exception{
MetaClass metaClass = MetaClass.forClass(TUser.class, new DefaultReflectorFactory());
TUser blankUser = new TUser();
metaClass.getSetInvoker("name").invoke(blankUser,new Object[]{"孫悟空"});
// 孫悟空
System.out.println(blankUser.getName());
}