MetaObject 通过反射机制访问 parameter 对象的类对象(Class 对象),为什么要修改类对象的属性值,类对象里都没有属性值,属性值在实例对象里呀,实例对象的属性值操作不需要反射呀?
场景:mybatisplus有自动生成主键的雪花算法策略,在实体类上,但是前提是数据库操作要走mybatisplus提供的BaseMapper里的insetr或update,
@GeneratedValue是jpa的注解,这里设置了自增,在生表阶段,不会设置id为雪花算法
将id赋值为雪花id是在哪个阶段呢,是在sql执行阶段,很好,那就是对应dao层框架了,他们封装了对于数据库的操作呀,尤其是mybatis,但很遗憾,如果你走的是mybatis的i定义接口和接口映射文件这一套,那就不能触发雪花id的生成策略了,因为mybatis实现操作数据库靠的是:依靠接口动态生成实现类,不要怀疑,真的有class对象和实例对象生成,再根据接口和实现类搞一个代理实现类的代理对象,这个代理对象里就是jdbc操作了,具体操作就看mapper.xml解析出来的内容了,所以mybatisse会调用代理对象的方法,所以,压根和baseMApepr没关系,
所以我们只能搞拦截器了,
@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})public class CustomIdInterceptor implements Interceptor, InnerInterceptor {private static final Logger logger = LoggerFactory.getLogger(CustomIdInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];// 如果是插入操作if ("insert".equalsIgnoreCase(mappedStatement.getSqlCommandType().toString())) {// 如果parameter是个List集合,那就是批量插入if (parameter instanceof java.util.List) {
// 遍历for (Object obj : (java.util.List) parameter) {MetaObject metaObject = SystemMetaObject.forObject(obj);
// 处理idif (metaObject.hasGetter("id")) {long id = IdUtil.getSnowflake().nextId();metaObject.setValue("id", id);}
// 处理deleted字段if (metaObject.hasGetter("deleted")) {metaObject.setValue("deleted", false);}// 处理创建时间if (metaObject.hasGetter("createdTime")) {metaObject.setValue("createdTime", java.time.LocalDateTime.now());}// 处理创建人if (metaObject.hasGetter("createdBy")) {metaObject.setValue("createdBy", UserUtil.getCurUser().getId());}}}else{
// 如果不是集合// 获取参数的mataObjiectMetaObject metaObject = SystemMetaObject.forObject(parameter);
// 处理idif (metaObject.hasGetter("id")) {long id = IdUtil.getSnowflake().nextId();metaObject.setValue("id", id);}
// 处理deleted字段if (metaObject.hasGetter("deleted")) {metaObject.setValue("deleted", false);}// 处理创建时间if (metaObject.hasGetter("createdTime")) {metaObject.setValue("createdTime", java.time.LocalDateTime.now());}// 处理创建人if (metaObject.hasGetter("createdBy")) {metaObject.setValue("createdBy", UserUtil.getCurUser().getId());}}}// 如果是更新操作if ("update".equalsIgnoreCase(mappedStatement.getSqlCommandType().toString())) {// 获取参数的mataObjiectMetaObject metaObject = SystemMetaObject.forObject(parameter);
// 处理更新时间if (metaObject.hasGetter("updatedTime")) {metaObject.setValue("updatedTime", java.time.LocalDateTime.now());}
// 处理更新人if(metaObject.hasGetter("updatedBy")){metaObject.setValue("updatedBy", UserUtil.getCurUser().getId());}}return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {return InnerInterceptor.super.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);}@Overridepublic void setProperties(Properties properties) {// 可以在这里设置拦截器的属性}
}
看这小拦截器,其中的mateObject和Excuter,MappedStatementd都是mybatise的核心对象,但是,看了调试上的实例对象的地址,我发现
这个参数其实是你的实体类,
MetaObject 通过反射机制访问 parameter 对象的类对象(Class 对象),为什么要修改类对象的属性值,类对象里都没有属性值,属性值在实例对象里呀,实例对象的属性值操作不需要反射呀?
记住我的神之回答
获取 类对象 不需要知道实例对象的类型,
1. 实例对象与类对象的区别
在 Java 中:
-
类对象(Class Object):
-
是类的运行时表示形式,存储在方法区(Metaspace)中。
-
包含类的元数据,例如字段、方法、构造函数等的定义。
-
不包含实例字段的值,因为实例字段的值是属于具体实例对象的。
-
-
实例对象(Instance Object):
-
是类的具体实例,存储在堆(Heap)中。
-
包含实例字段的实际值。
-
每个实例对象都有自己的字段值,这些值是独立的。
-
2. 为什么需要反射?
在你的代码中,MetaObject
通过反射机制访问和修改实例对象的字段值。这是因为:
-
动态性:
-
在运行时,你可能不知道
parameter
对象的具体类型。它可能是任意实现了接口的类的实例
-
-
-
反射机制允许你在运行时动态地访问和修改对象的字段,而无需在编译时知道具体类型。
-
-
封装性:
-
Java 的封装性使得字段通常是私有的(
private
),无法直接访问。 -
反射机制允许绕过封装性,直接访问和修改私有字段的值。
-
你用getter和setter每个方法都不一样,怎解,就问你怎解。。。。
-
就算是通过反射,类对象里只有字段,没有字段值,所以修改字段值必定是操作了实例对象,这个没跑,就看是怎么操作的了,
-
-
这是先将字段属性设置成外部可直接访问的,再对实例对象进行修改,改玩在设置成不可访问。。。。。。
-
-
-
-
通用性:
-
MetaObject
提供了一种通用的方式来操作对象的字段,而无需针对每个类编写特定的代码。 -
这使得拦截器可以处理任意类型的对象,只要这些对象有相应的字段。
-
3. 实例对象的属性值操作为什么需要反射?
虽然实例对象的属性值存储在实例对象中,但直接操作这些属性值需要知道对象的具体类型。例如:
java复制
MyEntity entity = (MyEntity) parameter;
entity.setId(IdUtil.getSnowflake().nextId());
这种方式的问题是:
-
类型硬编码:你需要知道
parameter
的具体类型(如MyEntity
),并且在代码中显式地进行类型转换。 -
不通用:如果
parameter
是其他类型的对象,这段代码就会失败。
使用反射的优势
通过反射,你可以动态地访问和修改字段值,而无需知道具体类型。例如:
java复制
MetaObject metaObject = SystemMetaObject.forObject(parameter);
if (metaObject.hasGetter("id")) {long id = IdUtil.getSnowflake().nextId();metaObject.setValue("id", id);
}
这种方式的优点是:
-
通用性:可以处理任意类型的对象,只要这些对象有
id
字段。 -
动态性:在运行时检查字段是否存在,并动态地设置值。
4. MetaObject 的实现原理
MetaObject
是 MyBatis 提供的一个工具类,它封装了反射操作,使得字段访问和修改更加方便。它的实现基于以下机制:
-
反射机制:
-
使用
Class
对象获取字段的定义。 -
使用
Field
对象访问和修改字段值。
-
-
动态代理:
-
MetaObject
提供了一种代理机制,使得你可以通过统一的接口访问和修改字段值,而无需直接使用反射 API。
-
-
缓存机制:
-
为了提高性能,
MetaObject
会缓存类的元数据,避免重复的反射操作。
-
5. 示例代码解析
在你的代码中:
java复制
MetaObject metaObject = SystemMetaObject.forObject(parameter);
if (metaObject.hasGetter("id")) {long id = IdUtil.getSnowflake().nextId();metaObject.setValue("id", id);
}
-
SystemMetaObject.forObject(parameter)
创建了一个MetaObject
实例,封装了对parameter
对象的反射操作。 -
hasGetter("id")
检查parameter
对象的类是否有id
字段。 -
setValue("id", id)
动态地为parameter
对象的id
字段设置值。
6. 总结
-
类对象(Class Object):存储类的元数据,不包含实例字段的值。
-
实例对象(Instance Object):存储实例字段的实际值。
-
反射机制:允许在运行时动态地访问和修改实例对象的字段值,而无需知道具体类型。
-
MetaObject:封装了反射操作,提供了一种通用的方式来操作实例对象的字段值。
通过反射和 MetaObject
,你的拦截器可以动态地为任意类型的对象设置字段值,而无需在编译时知道具体类型。这种机制使得拦截器具有很高的通用性和灵活性。