单一职责,开闭原则,依赖倒置等是代码设计和优化的原则。
** 拿个简单的例子来说:
- 用户表 sys_user ( 存放用户基本信息 ) , 2. 用户扩展信息表 tb_user_extend (存放用户扩展信息,关联用户id)
假设 : 一个需求,需要用户的基本信息,接口写好了,代码开发完成了。
假设 代码是这样的 :
@Override
public SysUser getUserOne(String mobile) {
return sysUserMapper.selectByMoblie(mobile);
}
** 假定的需求描述,只为提供思路 **
根据手机查询单表用户基本信息。
很简单的一个查询,返回要个简单的用户对象
–> 需求变更了,需要返回更多的用户信息 ,扩展信息一并返回。怎么办?
一般会想到,直接改代码,增加输出类字段,改查询,关联查询扩展表不就有了吗。
这样是可以,如果改的话就动了就动了两个地方 ,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;
}
二: 创建 增加字段的 通用注解
targetField 关联扩展表查询出来的需要字段,扩展表查询字段为 address,
beanClazz 需要调用的 Bean ,
methodName 调用 Bean 下执行的查询方法,
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 了,
以上代码 还要可以优化的地方,只仅仅只是一个思路。