用户表 sys_user ( 存放用户基本信息 ) , 2. 用户扩展信息表 tb_user_extend (存放用户扩展信息,关联用户id)
假设 : 一个需求,需要用户的基本信息,接口写好了,代码开发完成了。
假设 代码是这样的 :
@Override public SysUser getUserOne(String mobile) { return sysUserMapper.selectByMoblie(mobile); }
根据手机查询单表用户基本信息。
很简单的一个查询,返回要个简单的用户对象
需求变更了,需要返回更多的用户信息 ,扩展信息一并返回。怎么办?
从图1需求,变为图2的需求,需要增加返回信息,假设图2新增的字段信息是关联表的信息;
一般会想到,直接改代码,增加输出类字段,改查询,关联查询扩展表不就有了吗。
这样是可以,如果改的话就动了就动了两个地方 ,sql 的关联查询,增加输出类的字段信息;
改用 aop 就不用动这么多地方,只需要增加输出字段 ,加上注解就ok 了 。
废话不多说,直接上代码:
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(); }
@InterceptorExtend @Override public SysUser getUserOne(String mobile) { return sysUserMapper.selectByMoblie(mobile); }
就是在 这个方法 加另一个注解,表示,这个方法 更改了需求,需要执行 aop 里面的逻辑 来添加 扩展字段;
注解类 (这个注解啥也没有,仅做 拦截方法的标识 而已)
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface InterceptorExtend { }
整个逻辑处理和代码注释都在下面
@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 了,
以上代码 还要可以优化的地方,只仅仅只是一个思路。
希望各位大佬指点,码字不易