baomidou / mybatis-plus

An powerful enhanced toolkit of MyBatis for simplify development
https://baomidou.com
Apache License 2.0
16.41k stars 4.31k forks source link

如果在插件中使用装饰模式增强、并非jdk动态代理时,会导致com.baomidou.mybatisplus.core.toolkit.PluginUtils#realTarget获取实际处理对象失败 #6183

Closed bugCats closed 5 months ago

bugCats commented 5 months ago

如果在插件中使用装饰模式增强、并非jdk动态代理时,会导致com.baomidou.mybatisplus.core.toolkit.PluginUtils#realTarget获取实际处理对象失败

当前使用版本 mybatisplus所有版本

当前环境信息 Java8 + Mysql5.7

描述bug现象 之前使用mybatis,自定义了很多插件,插件使用装饰模式增强,并非JDK动态代理。 最近打算升级到mybatis-plus,发现在com.baomidou.mybatisplus.core.toolkit.PluginUtils#realTarget处报错,此方法并未分离出来实际对象。

提供问题复现步骤

拦截器示例:使用装饰模式增强StatementHandler.prepare

@Component
public class MyInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) {
        return null;
    }

    @Override
    public Object plugin(Object target) {
        if ( target instanceof StatementHandler ) {
            return new StatementHandlerWrap((StatementHandler) target); //此处只演示StatementHandler,装饰其他对象也会造成同样异常
        }
        return target;
    }

    private static class StatementHandlerWrap implements StatementHandler {
        private final StatementHandler h; //模拟jdk动态代理后的目标对象属性名
        private StatementHandlerWrap(StatementHandler statementHandler) {
            this.h = statementHandler;
        }

        @Override
        public Statement prepare(Connection connection, Integer timeout) throws SQLException {
            //增强代码
            return h.prepare(connection, timeout);
        }

        @Override
        public void parameterize(Statement statement) throws SQLException {
            h.parameterize(statement);
        }
        @Override
        public void batch(Statement statement) throws SQLException {
            h.batch(statement);
        }
        @Override
        public int update(Statement statement) throws SQLException {
            return h.update(statement);
        }
        @Override
        public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
            return h.query(statement, resultHandler);
        }
        @Override
        public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
            return h.queryCursor(statement);
        }
        @Override
        public BoundSql getBoundSql() {
            return h.getBoundSql();
        }
        @Override
        public ParameterHandler getParameterHandler() {
            return h.getParameterHandler();
        }
    }
}

异常信息:

Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'delegate' in 'class xxx.xxx.MyInterceptor$StatementHandlerWrap'
    at org.apache.ibatis.reflection.Reflector.getGetInvoker(Reflector.java:374)
    at org.apache.ibatis.reflection.MetaClass.getGetInvoker(MetaClass.java:164)
    at org.apache.ibatis.reflection.wrapper.BeanWrapper.getBeanProperty(BeanWrapper.java:162)
    at org.apache.ibatis.reflection.wrapper.BeanWrapper.get(BeanWrapper.java:49)
    at org.apache.ibatis.reflection.MetaObject.getValue(MetaObject.java:122)
    at com.baomidou.mybatisplus.core.toolkit.PluginUtils.mpStatementHandler(PluginUtils.java:72)
    at com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor.beforePrepare(BlockAttackInnerInterceptor.java:53)
    at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:102)
    ...

实际原因: com.baomidou.mybatisplus.core.toolkit.PluginUtils#realTarget,只有在入参target是JDK代理对象时,才会执行获取实际对象逻辑。

miemieYaho commented 5 months ago

你自己的东西让别人怎么取?

bugCats commented 5 months ago

你自己的东西让别人怎么取?

至少有2种方式:

1、简单粗暴,直接判断target是否包含h属性。这样就算是使用装饰模式,被装饰对象属性名如果为h,同样可以获取到真实对象:

        MetaObject handler = SystemMetaObject.forObject(target);
        while (handler.hasGetter("h")) {
            Object object = handler.getValue("h");
            handler = SystemMetaObject.forObject(object);
        }
        while (handler.hasGetter("target")) {
            Object object = handler.getValue("target");
            handler = SystemMetaObject.forObject(object);
        }

2、提供获取原始对象接口:如果使用装饰模式,装饰类再实现该接口,返回真实对象;

public interface PluginProxyAdapter {
    Object theProxyObject();
}

//com.baomidou.mybatisplus.core.toolkit.PluginUtils#realTarget,优先判断PluginProxyAdapter
public static <T> T realTarget(Object target) {
    if( target instanceof PluginProxyAdapter){
        return realTarget(((PluginProxyAdapter) target).theProxyObject());
    }
    if (Proxy.isProxyClass(target.getClass())) {
        MetaObject metaObject = SystemMetaObject.forObject(target);
        return realTarget(metaObject.getValue("h.target"));
    }
    return (T) target;
}

private static class StatementHandlerWrap implements StatementHandler, PluginProxyAdapter {
        private final StatementHandler h; //模拟jdk动态代理后的目标对象属性名
        private StatementHandlerWrap(StatementHandler statementHandler) {
            this.h = statementHandler;
        }

        @Override
        public Object theProxyObject() {
            return this.h;
        }

        ......

}

方法总比困难多

VampireAchao commented 5 months ago

问题解决了吗?我将关闭本issue,如需要可以reopen