转载

五步手把手教你摆脱冗余业务代码,了解 AOP,符合代码设计原则

单一职责,开闭原则,依赖倒置等是代码设计和优化的原则。

拿个简单的例子来说:

  1. 用户表 sys_user ( 存放用户基本信息 ) , 2. 用户扩展信息表 tb_user_extend (存放用户扩展信息,关联用户id)

    假设 : 一个需求,需要用户的基本信息,接口写好了,代码开发完成了。

    假设 代码是这样的 :

@Override 
public SysUser getUserOne(String mobile) { 
    return sysUserMapper.selectByMoblie(mobile); 
}

根据手机查询单表用户基本信息。

很简单的一个查询,返回要个简单的用户对象

需求变更了,需要返回更多的用户信息 ,扩展信息一并返回。怎么办?

五步手把手教你摆脱冗余业务代码,了解 AOP,符合代码设计原则

从图1需求,变为图2的需求,需要增加返回信息,假设图2新增的字段信息是关联表的信息;

一般会想到,直接改代码,增加输出类字段,改查询,关联查询扩展表不就有了吗。

这样是可以,如果改的话就动了就动了两个地方 ,sql 的关联查询,增加输出类的字段信息;

改用 aop 就不用动这么多地方,只需要增加输出字段 ,加上注解就ok 了 。

废话不多说,直接上代码:

一:增加 返回对象需要增加的字段 (假设 address 为 用户的 扩展字段,存放在 扩展信息表)

public class SysUser {
    private Long user_id; 
    private String user_name;
    private String pass_word;
    private String mobile;
    private Integer amount;
    private String address;
    ...
}

二: 创建 增加字段的 通用注解

1.targetField 关联扩展表查询出来的需要字段,扩展表查询字段为 address;
2.beanClazz 需要调用的 Bean ;
3.methodName 调用 Bean 下执行的查询方法;
4.paramFile 执行方法所需的参数.
@ExtendInfo(targetField="address",beanClazz=UserExtendMapper.class,methodName = "queryByUserId",paramFile = "user_id")
private String address;

注解类 (不用多说, 注解在关联查询的字段上)

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtendInfo {
    /**
     * 目标字段
     * @return
     */
    String targetField();

    /**
     * 参数
     * @return
     */
    String paramFile();

    /**
     * 需要调用的 bean
     * @return
     */
    Class beanClazz();

    /**
     * 需要调用的方法
     * @return
     */
    String methodName();
}

三 : 创建 aop 拦截的 注解 ,在需要更改的方法上做拦截,添加注解:

@InterceptorExtend
@Override
public SysUser getUserOne(String mobile) {
    return sysUserMapper.selectByMoblie(mobile);
}

就是在 这个方法 加另一个注解,表示,这个方法 更改了需求,需要执行 aop 里面的逻辑 来添加 扩展字段;

注解类 (这个注解啥也没有,仅做 拦截方法的标识 而已)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InterceptorExtend {
}

四 :aop 拦截 逻辑处理, 创建 aop :

整个逻辑处理和代码注释都在下面

@Component
@Aspect
public class InterceptorExtendHanddle {

    @Resource
    private ApplicationContext applicationContext;

    @Pointcut("execution(* com.example.service..*(..))")

    public void wePointCut() {}

    @Around("wePointCut() && @annotation(interceptorExtend)")
    public Object requestLimit(final ProceedingJoinPoint joinPoint, InterceptorExtend interceptorExtend) {
        Object proceed = null;
        try {
            proceed = joinPoint.proceed(); // 反射拿到方法返回结果
            MethodSignature signature = (MethodSignature)joinPoint.getSignature();
            Class returnType = signature.getReturnType(); // 返回结果类型
            boolean assignableFrom = returnType.isAssignableFrom(List.class); // 判断是否为集合,有的方法为单个对象,有的为集合
            if (assignableFrom) {
                // 返回是 list
                List proceed1 = (List) proceed;
                for (int i = 0; i < proceed1.size(); i++) {
                    Object o = proceed1.get(i);
                    fillValue2Field(o); // 填充扩展字段值
                }
                return proceed;
            }
            // 非集合,单个对象
            fillValue2Field(proceed);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return proceed;
    }

    /**
     * 填充扩展字段值
     * @param proceed
     * @throws Exception
     */
    private void fillValue2Field(Object proceed) throws Exception {
        Field[] declaredFields = proceed.getClass().getDeclaredFields();//拿到返回对象字段
        for (Field declaredField : declaredFields) {//遍历字段
            declaredField.setAccessible(true);
            System.out.println("字段名" + declaredField.getName()+",值:"+declaredField.get(proceed)); // 输出一下,看看是否拿到属性和值
            ExtendInfo extendInfo = declaredField.getAnnotation(ExtendInfo.class); //拿到属性字段是否有扩展注解,往下判断是否需要关联查询
            if (ObjectUtils.isEmpty(extendInfo)) {
                continue; // 没有扩展注解直接跳过
            }
            String targetFieldStr = extendInfo.targetField(); // 关联扩展表查询出来的需要字段
            String paramFieldStr = extendInfo.paramFile();  // 执行方法所需的参数
            String methodName = extendInfo.methodName(); //调用 Bean 下执行的查询方法
            Class beanClazz = extendInfo.beanClazz();  // 需要调用的 Bean
            System.out.println("目标字段"+targetFieldStr);
            System.out.println("参数字段"+paramFieldStr);
            System.out.println("方法"+methodName);
            Object beanResultObj = invokeBeanMethod(beanClazz, methodName, paramFieldStr, proceed); // spring 加载bean ,执行bean 方法,拿到关联查询的结果
            changeValue(declaredField,targetFieldStr,beanResultObj,proceed); // 将 查询的结果,和需要填充的扩展字段 传入该方法,来改变扩展字段的值
        }
        //System.out.println(JSONObject.toJSONString(proceed));
    }

    /**
     * 加载bean ,执行 bean 方法,拿到关联查询结果
     * @param beanClazz  所需加载的 bean
     * @param methodName  注解里 的 bean 方法
     * @param paramFieldStr  注解里 参数字段
     * @param proceed  aop 拦截的结果对象
     * @return Object
     * @throws Exception
     */
    private Object invokeBeanMethod(Class<Object> beanClazz,String methodName,String paramFieldStr,Object proceed) throws Exception{
        Field paramField = proceed.getClass().getDeclaredField(paramFieldStr); // 参数字段
        paramField.setAccessible(true);  // 私有字段,设置为true
        Object bean = applicationContext.getBean(beanClazz); // 拿到bean
        applicationContext.getAutowireCapableBeanFactory().autowireBean(bean); // 装载 bean
        Method declaredMethod = beanClazz.getDeclaredMethod(methodName, paramField.getType()); //反射代理方法。getDeclaredMethod 方法 ,第一个参数是 方法名,第个参数是参数类型
        declaredMethod.setAccessible(true);
        return  declaredMethod.invoke(bean, paramField.get(proceed));  // 执行bean 方法,并返回查询的结果
    }

    /**
     * 添加需要扩展字段的值
     * @param currentField  当前有注解,需要扩展信息的属性字段
     * @param targetFieldStr  关联扩展表查询出来的需要字段
     * @param beanResultObj  注解bean 执行的 查询 结果
     * @param aopProceed   aop 拦截的结果对象,应为是要在 aop拦截的结果对象里面 重新给扩展字段赋值, 这里需要传入
     * @throws Exception
     */
    private void changeValue(Field currentField,String targetFieldStr,Object beanResultObj,Object aopProceed) throws Exception {
        Class<?> beanResultObjClazz = beanResultObj.getClass();
        Field targetField = beanResultObjClazz.getDeclaredField(targetFieldStr); // 获取 bean 查询的 目标字段
        targetField.setAccessible(true);
        Object o = targetField.get(beanResultObj);  // 获取 bean 查询的目标字段值
        currentField.setAccessible(true);
        currentField.set(aopProceed,o);  // 赋值给 当前有注解需要扩展字段
    }
}

五 :总结:

通过上述的处理,在不改变原有方法的情况下,增加的需求,这是可以通用的,再增加查询字段 ,只需在输出的对象里面 添加 注解就 OK 了,

以上代码 还要可以优化的地方,只仅仅只是一个思路。

希望各位大佬指点,码字不易

原文  https://segmentfault.com/a/1190000022506712
正文到此结束
Loading...