mybatis拦截器使用及原理

您所在的位置:网站首页 metaobject mybatis拦截器使用及原理

mybatis拦截器使用及原理

2024-07-05 09:37| 来源: 网络整理| 查看: 265

使用拦截器

Web开发中我们经常会碰到分页操作,一个项目中或许有多处使用到分页,这时如果Java后台使用MyBatis作为持久层,我们就可以使用MyBatis的拦截器功能来完成整个项目中多处的分页操作,减少代码的冗余。

拦截器代码

//拦截StatementHandler中参数类型为Connection的prepare方法 @Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})}) public class PageInterceptor implements Interceptor { private String test; // 获取xml中配置的属性 @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler)invocation.getTarget(); //通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性 MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); //先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement"); // 配置文件中SQL语句的ID String id = mappedStatement.getId(); if(id.matches(".+ByPage$")) { //需要拦截的ID(正则匹配) BoundSql boundSql = statementHandler.getBoundSql(); // 原始的SQL语句 String sql = boundSql.getSql(); // 查询总条数的SQL语句 String countSql = "select count(*) from (" + sql + ")a"; //执行总条数SQL语句的查询 Connection connection = (Connection)invocation.getArgs()[0]; PreparedStatement countStatement = connection.prepareStatement(countSql); 获取参数信息即where语句的条件信息,注意上面拿到的sql中参数还是用?代替的 ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler"); parameterHandler.setParameters(countStatement); ResultSet rs = countStatement.executeQuery(); Map parameter = (Map)boundSql.getParameterObject(); Page page = (Page)parameter.get("page"); if(rs.next()) { page.setTotalNumber(rs.getInt(1)); } // 改造后带分页查询的SQL语句 String pageSql = sql + " limit " + page.getDbIndex() + "," + page.getDbNumber(); metaObject.setValue("delegate.boundSql.sql", pageSql); } return invocation.proceed(); } @Override public Object plugin(Object target) { System.out.println(this.test); return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { this.test = properties.getProperty("test"); // TODO Auto-generated method stub } }

MetaObject是Mybatis提供的一个用于方便、优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。获取MetaObject对象需要使用静态方法MetaObject.forObject,并且需要指定ObjectFactory , ObjectWrapperFactory , ReflectorFactory(3.3.0之前不需要)。

配置拦截器

这里配置test的值为abc,那么上面拦截器中test的就会被赋值为abc。

查询SQL

select from MESSAGE and COMMAND=#{message.command} and DESCRIPTION like '%' #{message.description} '%' order by ID ID,COMMAND,DESCRIPTION,CONTENT

调用示例

/** * 根据查询条件分页查询消息列表 */ public List queryMessageListByPage(String command,String description,Page page) { Map parameter = new HashMap(); // 组织消息对象 Message message = new Message(); message.setCommand(command); message.setDescription(description); parameter.put("message", message); parameter.put("page", page); MessageDao messageDao = new MessageDao(); // 分页查询并返回结果 return messageDao.queryMessageListByPage(parameter); }

完整代码:http://download.csdn.net/download/zxc123e/9930719

拦截器设计原理

拦截器的实现都是基于代理的设计模式实现的,简单的说就是要创造一个目标类的代理类,在代理类中执行目标类的方法并在方法之前执行拦截器代码。 首先,先不管mybatis的源码是怎么设计的,先假设一下自己要做一个拦截器应该怎么做。下面我们就利用JDK的动态代理自己设计一个简单的拦截器。

将被拦截的目标接口:

public interface Target { public void execute(); }

目标接口的一个实现类:

public class TargetImpl implements Target { public void execute() { System.out.println("Execute"); } }

利用JDK的动态代理实现拦截器:

public class TargetProxy implements InvocationHandler { private Object target; private TargetProxy(Object target) { this.target = target; } //生成一个目标对象的代理对象 public static Object bind(Object target) { return Proxy.newProxyInstance(target.getClass() .getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target)); } //在执行目标对象方法前加上自己的拦截逻辑 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Begin"); return method.invoke(target, args); } }

客户端调用:

public class Client { public static void main(String[] args) { //没有被拦截之前 Target target = new TargetImpl(); target.execute(); //Execute //拦截后 target = (Target)TargetProxy.bind(target); target.execute(); //Begin //Execute }

上面的设计有几个非常明显的不足。首先,拦截逻辑被写死在代理对象中:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //拦截逻辑被写死在代理对象中,导致客户端无法灵活的设置自己的拦截逻辑 System.out.println("Begin"); return method.invoke(target, args); }

我们可以将拦截逻辑封装到一个类中,客户端在调用TargetProxy的bind()方法的时候将拦截逻辑一起当成参数传入:  定义一个拦截逻辑封装的接口Interceptor,这才是真正的拦截器接口。

public interface Interceptor { public void intercept(); }

那么我们的代理类就可以改成:

public class TargetProxy implements InvocationHandler { private Object target; private Interceptor interceptor; private TargetProxy(Object target, Interceptor interceptor) { this.target = target; this.interceptor = interceptor; } //将拦截逻辑封装到拦截器中,有客户端生成目标类的代理类的时候一起传入,这样客户端就可以设置不同的拦截逻辑。 public static Object bind(Object target, Interceptor interceptor) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target, interceptor)); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //执行客户端定义的拦截逻辑 interceptor.intercept(); return method.invoke(target, args); }

客户端调用代码:

/客户端可以定义各种拦截逻辑 Interceptor interceptor = new Interceptor() { public void intercept() { System.out.println("Go Go Go!!!"); } }; target = (Target)TargetProxy.bind(target, interceptor); target.execute();

当然,很多时候我们的拦截器中需要判断当前方法需不需要拦截,或者获取当前被拦截的方法参数等。我们可以将被拦截的目标方法对象,参数信息传给拦截器。  拦截器接口改成:

public interface Interceptor { public void intercept(Method method, Object[] args); }

在代理类执行的时候可以将当前方法和参数传给拦截,即TargetProxy的invoke方法改为:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { interceptor.intercept(method, args); return method.invoke(target, args); }

在Java设计原则中有一个叫做迪米特法则,大概的意思就是一个类对其他类知道得越少越好。其实就是减少类与类之间的耦合强度。这是从类成员的角度去思考的。 什么叫越少越好,什么是最少?最少就是不知道。 所以我们是不是可以这么理解,一个类所要了解的类应该越少越好呢?

对于上面的例子,Interceptor接口中需要使用intercept方法传过去Method类,那么也需要了解它。那么既然Interceptor都需要使用Method,还不如将Method的执行也放到Interceptor中,不再让TargetProxy类对其了解。Method的执行需要target对象,所以也需要将target对象给Interceptor。将Method,target和args封装到一个对象Invocation中,将Invocation传给Interceptor。

Invocation.java public class Invocation { private Object target; private Method method; private Object[] args; public Invocation(Object target, Method method, Object[] args) { this.target = target; this.method = method; this.args = args; } //将自己成员变量的操作尽量放到自己内部,不需要Interceptor获得自己的成员变量再去操作它们, //除非这样的操作需要Interceptor的其他支持。然而这里不需要。 public Object proceed() throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public Object[] getArgs() { return args; } public void setArgs(Object[] args) { this.args = args; } } TargetProxy.java public class TargetProxy implements InvocationHandler { private Object target; private Interceptor interceptor; private TargetProxy(Object target, Interceptor interceptor) { this.target = target; this.interceptor = interceptor; } //将拦截逻辑封装到拦截器中,有客户端生成目标类的代理类的时候一起传入,这样客户端就可以设置不同的拦截逻辑。 public static Object bind(Object target, Interceptor interceptor) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target, interceptor)); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return interceptor.intercept(new Invocation(target, method, args)); } } 测试类 public class Client { public static void main(String[] args) { Interceptor interceptor = invocation -> { System.out.println("Go Go Go!!!"); return invocation.proceed(); }; Target target = (Target) TargetProxy.bind(new TargetImpl(), interceptor); target.execute(); } }

根据迪米特法则来讲,其实客户端根本不需要了解TargetProxy类。将绑定逻辑放到拦截器内部,客户端只需要和拦截器打交道就可以了。

拦截器接口变为:

public interface Inteceptor { Object intercept(Invocation invocation)throws Throwable; Object register(Object target); }

拦截器实现类

public class InteceptorImpl implements Inteceptor { @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("Go Go Go!!!"); return invocation.proceed(); } @Override public Object register(Object target) { return TargetProxy.bind(target, this); } }

测试类

public class Client { public static void main(String[] args) { Inteceptor interceptor = new InteceptorImpl(); Target target = (Target) interceptor.register(new TargetImpl()); target.execute(); } }

好了,通过一系列调整,设计已经挺好了,不过上面的拦截器还是有一个很大的不足, 那就是拦截器会拦截目标对象的所有方法,然而这往往是不需要的,我们经常需要拦截器拦截目标对象的指定方法。 我们利用在Interceptor上加注解解决。

假设目标对象接口有多个方法:

public interface Target { public void execute1(); public void execute2(); }

首先简单的定义一个注解:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MethodName { public String value(); }

在拦截器的实现类加上该注解:

@MethodName("execute1") public class InteceptorImpl implements Inteceptor { ... }

在TargetProxy中判断interceptor的注解,看是否实行拦截:

@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodName methodName = this.interceptor.getClass().getAnnotation(MethodName.class); if (methodName == null) throw new NullPointerException("xxxx"); //如果注解上的方法名和该方法名一样,才拦截 String name = methodName.value(); if (name.equals(method.getName())) return interceptor.intercept(new Invocation(target, method, args)); return method.invoke(this.target, args); }

 

OK,上面的一系列过程其实都是mybatis的拦截器代码结构,上面的TargetProxy其实就是mybatis的Plugin类。Interceptor和Invocation几乎一样。只是mybatis的Interceptor支持的注解更加复杂。

MyBatis的Plugin类,看到里面的两个方法,现在就不那么陌生了。

public class Plugin implements InvocationHandler { private Object target; private Interceptor interceptor; private Map, Set> signatureMap = getSignatureMap(interceptor); Class type = target.getClass(); Class[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } ... }

 

mybatis最终是通过将自定义的Interceptor配置到xml文件中:



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3