利用SpringAOP 对 Mybatis Pagehelper 使用上的优化

1、前言

Mybatis 的 Pagehelper 插件相信大家都使用过(没用过的请飘过~~~~),并且用起来确实很方便。但是每次都的PageHelper.startPage(PageNum, PageSize),对于我这种比较懒的人来说,是万万忍受不了的,怎么办?那就的想一劳永逸的方法了。

废话就不多说了,下面直接上code

2、调用方式改变

正常流程,伪代码

//controller
@PostMapping("/")
public Object findUserList(int otherParams, int pageNum, int pageSize) {
    // 参数处理
    // 业务处理
    Object users = service.findUserByPage(pageNum, pageSize);
    // 业务处理
    
    return Object;
}

// service
public Object findUserByPage(int pageNum, int pageSize) {
    PageHelper.startPage(PageNum, PageSize);
    
    // 业务处理
    // 结果处理
    
    return Object;
}

正常我们使用MyBatis PageHelper 分页插件基本就是上面写法(只是伪代码,有错误请多多包涵)

改造后,流程

//controller
@ControllerPagehelper
@PostMapping("/")
public Object findUserList(int otherParams, int pageNum, int pageSize) {
    // 参数处理
    // 业务处理
    Object users = service.findUserByPage();
    // 业务处理
    
    return Object;
}

// service
@ServicePagehelper
public Object findUserByPage() { 
    // 业务处理
    // 结果处理
    
    return Object;
}

变化大家一眼就可看到,我就不啰嗦了, 下面直接撸代码。

3、 实现

3.1 创建 ControllerPagehelper 注解

@Target({ElementType.METHOD}) // 作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行期间
@Documented
public @interface ControllerPagehelper {

}

3.2 创建 ServicePagehelper 注解

@Target({ElementType.METHOD}) // 作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行期间
@Documented
public @interface ServicePagehelper {

}

3.3 AOP实现拦截

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.javassist.ClassClassPath;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.javassist.Modifier;
import org.apache.ibatis.javassist.bytecode.CodeAttribute;
import org.apache.ibatis.javassist.bytecode.LocalVariableAttribute;
import org.apache.ibatis.javassist.bytecode.MethodInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.github.pagehelper.PageHelper;
import com.ochain.mall.product.dto.input.PageInputDTO;

import lombok.extern.slf4j.Slf4j;

/**
 * 分页拦截
 * 
 * @author yueli
 * @date Feb 25, 2019 11:56:54 AM
 */
@Aspect
@Component
@Slf4j
@Order(3)
public class PagehelperAspect {
    
    // ThreadLocal 存放 分也参数
    private static final ThreadLocal<PageInputDTO> PAGE_INPUTDTO_CONTEXT = new ThreadLocal<>();
    
    // 参数类型
    private static String[] types = { "java.lang.Integer", "java.lang.Double", "java.lang.Float", "java.lang.Long",
            "java.lang.Short", "java.lang.Byte", "java.lang.Boolean", "java.lang.Char", "java.lang.String", "int",
            "double", "long", "short", "byte", "boolean", "char", "float" };
    
    
    private static final String CURRENTPAGE = "pageNum";
    private static final String PAGESIZE = "pageSize";
    
    
    @Pointcut("@annotation(com.annotations.ServicePagehelper)")
    public void pageServiceAspect() {
    }

    @Pointcut("@annotation(com.annotations.ControllerPagehelper)")
    public void pageControllerAspect() {
    }

    
    @Before("pageControllerAspect()")
    public void controllerAop(JoinPoint joinPoint) throws Exception {
        
        log.info("ControllerAop ->>> 开会分页拦截");
        // PageInputDTO 分装分也信息
        PageInputDTO pageInputDTO = null;

        Object[] args = joinPoint.getArgs();
        // 获取类名
        String clazzName = joinPoint.getTarget().getClass().getName();
        // 获取方法名称
        String methodName = joinPoint.getSignature().getName();

        // 通过反射获取参数列表
        Map<String, Object> nameAndArgs = this.getFieldsName(this.getClass(), clazzName, methodName, args);

        Object object = nameAndArgs.get("pageInputDTO");
        if (null != object) {
            pageInputDTO = (PageInputDTO) object;
        } else {
            pageInputDTO = new PageInputDTO();
            pageInputDTO.setPageNum(
                    (Integer) nameAndArgs.get(CURRENTPAGE) == null ? 0 : (int) nameAndArgs.get(CURRENTPAGE));
            pageInputDTO.setPageSize(
                    ((Integer) nameAndArgs.get(PAGESIZE) == null || (Integer) nameAndArgs.get(PAGESIZE) <= 0) ? 10
                            : (Integer) nameAndArgs.get(PAGESIZE));
        }

        // 将分页参数放置线程变量中
        PAGE_INPUTDTO_CONTEXT.set(pageInputDTO);
    }

    @Before("pageServiceAspect()")
    public void serviceImplAop() throws Throwable {
        log.info("Service正在执行PageHelperAop");
        
        PageInputDTO pageBean = PAGE_INPUTDTO_CONTEXT.get();
        
        PageHelper.startPage(pageBean.getPageNum(), pageBean.getPageSize());
        
        // ** 使用完成ThreadLocal后必须调用remove方法,防止内存溢出
        PAGE_INPUTDTO_CONTEXT.remove();
    }

    private Map<String, Object> getFieldsName(@SuppressWarnings("rawtypes") Class cls, String clazzName,
            String methodName, Object[] args) throws Exception {
        
        Map<String, Object> map = new HashMap<String, Object>(8);

        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(cls);
        pool.insertClassPath(classPath);
        CtClass cc = pool.get(clazzName);
        CtMethod cm = cc.getDeclaredMethod(methodName);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            return new HashMap<>(1);
        }
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < cm.getParameterTypes().length; i++) {
            Object arg = args[i];
            log.info("advice -- >> arg{}, arg#class:{}", arg, arg==null ? null : arg.getClass());
            
            // 如果controller中使用PageInputDTO 来接收分页参数的,直接将其保存起来
            if (arg instanceof PageInputDTO) {
                map.put("pageInputDTO", arg);
                break;
            }
             
            if (arg == null || arg.getClass() == null) {
                continue;
            }

            if (CURRENTPAGE.equals(attr.variableName(i + pos)) || PAGESIZE.equals(attr.variableName(i + pos))) {
                map.put(attr.variableName(i + pos), arg);
                continue;
            }
            
            // 因我们只取 pageNum 和 pageSize 取到多就直接返回,不用在遍历。
            if (map.size() >= 2) {
                break;
            }
  
            if (!Arrays.asList(types).contains(arg.getClass().getTypeName())) {
                Class<?> superclass = arg.getClass().getSuperclass();
                if (superclass != null) {
                    Object newInstance = superclass.newInstance();
                    if (newInstance instanceof PageInputDTO) {
                        map.put("pageInputDTO", arg);
                        break;
                    }
                }
            }

        }

        return map;
    }

    public static void getFieldsValue(Object obj, Map<String, Object> map)
            throws IllegalArgumentException, IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field f : fields) {
            f.setAccessible(true);
            String name = f.getName();
            if (CURRENTPAGE.equals(name) || PAGESIZE.equals(name)) {
                map.put(f.getName(), f.get(obj));
            }
        }
    }
}

3.4 辅助类pageInputDTO

/**
 * 分页请求参数
 * 
 * @author yueli
 * @date Feb 22, 2019 1:03:52 PM
 */
@Data
public class PageInputDTO {

    @ApiModelProperty(value = "当前页默认值:0", dataType = "Integer")
    private Integer pageNum;

    @ApiModelProperty(value = "每页显示条数默认值:10", dataType = "Integer")
    private Integer pageSize;

}

4 总结

到这就搞定了, 这样以来我们在使用分页时是不是简单了很多。

改进:
1: 如果觉得注解使用起来还是不爽,可以将切面,改成拦截固定结尾的方法如**bypage等
2: pageControllerAspect 的切面可以改进为 Around 将结果也进行处理, 不过这个处理起来比较麻烦,个人感觉有点得不偿失, 如想处理,自己实现下也不难。

OK ! 结束了, 希望对你有所帮助, 如有不当或者需要改进的地方,请留言。
谢谢!

相关推荐