利用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 ! 結束了, 希望對你有所幫助, 如有不當或者需要改進的地方,請留言。
謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章