Java 在不更改原始方法签名的情况下对服务方法进行分页调用

Java 在不更改原始方法签名的情况下对服务方法进行分页调用,java,lambda,pagination,java-8,mockito,Java,Lambda,Pagination,Java 8,Mockito,我有一个用例,需要向现有服务调用(返回结果列表)添加分页输入(即页码和页面大小),而不更改现有签名(因为它会破坏现有客户端)。实现这一点的一种方法是在threadlocal中设置输入,让实现读取threadlocal并执行其分页逻辑。从代码的角度来看,它看起来像: try { PaginationKit.setPaginationInput(pageSize, pageNumber); // Set threadlocal List<SpecialObject> re

我有一个用例,需要向现有服务调用(返回结果列表)添加分页输入(即页码和页面大小),而不更改现有签名(因为它会破坏现有客户端)。实现这一点的一种方法是在threadlocal中设置输入,让实现读取threadlocal并执行其分页逻辑。从代码的角度来看,它看起来像:

try {
    PaginationKit.setPaginationInput(pageSize, pageNumber); // Set threadlocal
    List<SpecialObject> results = specialService.getSpecialObjs(); //results count will be equal to pageSize value
} finally {
    PaginationKit.clearPaginationInput(); // Clear threadlocal
}
我试图借用Mockito的代码来创建spy,但是
ongoingstubing
接口在随后的调用链接/创建代码中交织在一起,并且有一种我应该避免的味道

方法2: 使用java.util.Function捕获方法调用,并接受另外两个参数pageNumber和pageSize以使用threadlocals。代码可能看起来像

List<SpecialObject> results = PaginationDecorator.withPaging(specialService.getSpecialObjs(), pageSize, pageNumber);
List results=PaginationDecorator.withPaging(specialService.GetSpecialLobJS(),pageSize,pageNumber);
PaginationCorator.java:

public static List<T> withPaging(Function<U, List<T>> call, int pageSize, int pageNumber) {
    try {
        PaginationKit.setPaginationInput(pageSize, pageNumber); // Set threadlocal
        return call.apply(); // Clearly, something is missing here!
    } finally {
        PaginationKit.clearPaginationInput(); // Clear threadlocal
    }
}
带分页的公共静态列表(函数调用、int pageSize、int pageNumber){
试一试{
PaginationKit.setPaginationInput(pageSize,pageNumber);//设置threadlocal
return call.apply();//显然,这里缺少一些东西!
}最后{
PaginationKit.clearPaginationInput();//Clear threadlocal
}
}
在这里,我无法清楚地说明如何正确使用call

谁能告诉我:

  • 这两种方法中哪一种更好
  • 如果这是一个配方,其他地方可以使用不同的方法
  • 建议实施方法1或2的前进方向。就我个人而言,2对我来说似乎更干净(如果可行的话)
请随意评论该方法,并提前感谢阅读


附言:我也喜欢,但是语法糖的主要问题仍然需要解决。

您正在使用Java8,对吗?也许您可以添加一个默认方法,而无需任何实现(我知道这听起来有点奇怪),如下所示:

// Your current service interface
interface ServiceInterface <T> {

    // Existing method
    List<T> getObjects();

    // New method with pagination atributes
    default List<T> getObjects(int pageSize, int pageNumber) {
        throw new UnsupportedOperationException();
    }
}
//您当前的服务接口
接口服务接口{
//现有方法
列出getObjects();
//包茎包茎的新方法
默认列表getObjects(int pageSize、int pageNumber){
抛出新的UnsupportedOperationException();
}
}

新服务(支持分页)必须重写此方法。在我看来,这样你就能“保持简单”。

你的第二种方法对于这项任务来说似乎更干净、更容易。至于实现,您可以只使用Java的
代理
类。这似乎很容易。我不知道有哪家图书馆能让它变得更简单

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class PagingDecorator {
    public static void setPaginationInput(int pageSize, int pageNumber) {
    }

    public static void clearPaginationInput() {
    }

    public static <T> T wrap(final Class<T> interfaceClass, final T object, final int pageSize, final int pageNumber) {
        if (object == null) {
            throw new IllegalArgumentException("argument shouldn't be null");
        }

        ClassLoader classLoader = object.getClass().getClassLoader();
        return (T) Proxy.newProxyInstance(classLoader, new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                setPaginationInput(pageSize, pageNumber);
                try {
                    return method.invoke(object, args);
                } catch (InvocationTargetException e) {
                    throw e.getTargetException();
                } catch (Exception e) {
                    throw e;
                } finally {
                    clearPaginationInput();
                }
            }
        });
    }

    public static <T> T wrap(final T object, final int pageSize, final int pageNumber) {
        if (object == null) {
            throw new IllegalArgumentException("argument shouldn't be null");
        }

        Class<?>[] iFaces = object.getClass().getInterfaces();

        //you can use all interfaces, when creating proxy, but it seems cleaner to only mock the concreate interface that you want..
        //unfortunately, you can't just grab T as interface here, becuase of Java's generics' mechanic
        if (iFaces.length != 1) {
            throw new IllegalArgumentException("Object implements more than 1 interface - use wrap() with explicit interface argument.");
        }

        Class<T> iFace = (Class<T>) iFaces[0];
        return wrap(iFace, object, pageSize, pageNumber);
    }

    public interface Some {
    }

    public static void main(String[] args) {
        Some s = new Some() {};

        Some wrapped1 = wrap(Some.class, s, 20, 20);
        Some wrapped2 = wrap(s, 20, 20);
    }
}
import java.lang.reflect.InvocationHandler;
导入java.lang.reflect.InvocationTargetException;
导入java.lang.reflect.Method;
导入java.lang.reflect.Proxy;
公共类分页装饰器{
公共静态void setPaginationInput(int pageSize,int pageNumber){
}
公共静态无效clearPaginationInput(){
}
公共静态T换行(最终类interfaceClass、最终T对象、最终int pageSize、最终int pageNumber){
if(object==null){
抛出新的IllegalArgumentException(“参数不应为null”);
}
ClassLoader ClassLoader=object.getClass().getClassLoader();
return(T)Proxy.newProxyInstance(类加载器,新类[]{interfaceClass},新调用处理程序(){
@凌驾
公共对象调用(对象代理、方法、对象[]args)抛出Throwable{
设置分页输入(页面大小、页码);
试一试{
返回方法.invoke(对象,参数);
}捕获(调用TargetException e){
抛出e.getTargetException();
}捕获(例外e){
投掷e;
}最后{
clearPaginationInput();
}
}
});
}
公共静态T换行(最终T对象、最终int pageSize、最终int pageNumber){
if(object==null){
抛出新的IllegalArgumentException(“参数不应为null”);
}
类[]iFaces=object.getClass().getInterfaces();
//在创建代理时,您可以使用所有接口,但只模拟所需的concreate接口似乎更简洁。。
//不幸的是,由于Java的泛型机制,您不能在这里仅仅获取t作为接口
如果(iFaces.length!=1){
抛出新的IllegalArgumentException(“对象实现多个接口-使用wrap()和显式接口参数”);
}
类iFace=(类)iFace[0];
返回换行符(iFace、object、pageSize、pageNumber);
}
公共界面{
}
公共静态void main(字符串[]args){
Some s=新Some(){};
Some wrapped1=wrap(Some.class,s,20,20);
一些包裹2=包裹(s,20,20);
}
}

您的第二个变体不起作用,因为您使用了错误的接口(
函数
需要输入参数),并且没有语法来创建函数实例,而只是一个普通的调用表达式

你有几个选择

  • 使用
    供应商
    。此接口描述一个没有参数且返回值的函数

    public static <T> T withPaging(Supplier<T> call, int pageSize, int pageNumber) {
        try {
            PaginationKit.setPaginationInput(pageSize, pageNumber); // Set threadlocal
            return call.get();
        } finally {
            PaginationKit.clearPaginationInput(); // Clear threadlocal
        }
    }
    
    或lambda表达式:

    List<SpecialObject> results=PaginationDecorator.withPaging(
                        () -> specialService.getSpecialObjs(), pageSize, pageNumber);
    
    List<SpecialObject> results=PaginationDecorator.withPaging(
            ss -> ss.getSpecialObjs(), specialService, pageSize, pageNumber);
    
    现在,调用者必须提供一个函数和一个值。由于预期的方法是实例方法,因此可以将接收方实例视为函数参数

    然后,该函数也可以再次指定为(现在未绑定)方法引用

    List<SpecialObject> results=PaginationDecorator.withPaging(
                        specialService::getSpecialObjs, pageSize, pageNumber);
    
    List<SpecialObject> results=PaginationDecorator.withPaging(
            SpecialService::getSpecialObjs, specialService, pageSize, pageNumber);
    
    像这样使用它

    List<SpecialObject> results;
    try(Paging pg=Paging.withPaging(pageSize, pageNumber)) {
        results=specialService.getSpecialObjs();
    }
    
    列出结果;
    try(Paging pg=Paging.withPaging(pageSize,pageNumber)){
    结果=specialService.getSpecialLobJS();
    }
    
    其优点是,这不会中断与预期操作相关的代码流,即与lambda表达式不同,您可以修改所有
    List<SpecialObject> results=PaginationDecorator.withPaging(
            ss -> ss.getSpecialObjs(), specialService, pageSize, pageNumber);
    
    interface Paging extends AutoCloseable {
        void close();
        static Paging withPaging(int pageSize, int pageNumber) {
            PaginationKit.setPaginationInput(pageSize, pageNumber);
            return ()->PaginationKit.clearPaginationInput();
        }
    }
    
    List<SpecialObject> results;
    try(Paging pg=Paging.withPaging(pageSize, pageNumber)) {
        results=specialService.getSpecialObjs();
    }