场景:获取订单信息,其中需要用户名,封装需准备如下:
- 方法的注解:利用aop通过该方法注解进行切入目标方法增强目标方法的结果
- 字段的注解:利用反射通过该字段注解获取是那个类执行了那个方法,其方法是那个入参,返回结果中取那个目标字段
需要:1、那个类;2、那个方法;3、那个入参;4、那个目标字段
- aop切面类:动态代理增强
- beanUtil工具类:增强的实现过程
- 用户实体类:id、name。。。
- 订单实体类:id、customId、customName(加上字段注解)
- 用户mapper:findUserById方法,入参为customId
- 订单mapper:findOrderList
- 订单service:findOrderList,该方法加上方法注解,需要对目标方法的结果进行增强
- 用户service:findUserById方法,测试该方法
- @Test:注入service,进行测试
- 入口类:添加@MapperScan注解
代码示例:
/**
* 方法注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SetValue {
}
/**
* 字段注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SetValueField {
//@SetValueField(beanClass = UserMapper.class,param = "customId",method = "findUserById",targetField = "name")
//method.invoke(bean,args) 通过传参的形式告诉
//反射需要知道的类
Class<?> beanClass();
//反射执行所需的方法名
String method();
//反射方法的入参名
String paramField();
//指的是dao层的返回对象结果中的的所需字段名,可以通过该名字拿到具体的值
String targetField();
}
/**
* @description:自定义业务封装的aop切面
*/
@Component
@Aspect
public class SetFieldValueAspect {
@Autowired
private BeanUtil beanUtil;
@Around("@annotation(com.ldc.annotation_reflection_aop.annotation.SetValue)")
public Object doSetFieldValue(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//前置增强
System.out.println("前置增强操作");
Object result = proceedingJoinPoint.proceed();
//List = [{id:1,customId:1,customName:null @SetValueField}]
//向customName赋值》获取目标对象的结果对象
//后置增强
System.out.println("后置增强操作");
beanUtil.setFieldValue((Collection) result);
return result;
}
}
@Component
public class BeanUtil implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void setFieldValue(Collection collection) throws Exception {
//1获取结果集中的对象
//2获取该对象的类元信息
//3通过类元信息获取该对象的所有字段
//4遍历所有字段
//5有注解的字段则需要遍历结果集,将每个对象的该字段赋值,此时需要结果集中对象及目标对象的目标字段的值
//6结果集中对象已有,目标对象需要通过下面获取
//1通过注解获取目标对象的实例
//2通过注解获取目标对象的目标方法名
//3通过注解获取结果集中对象的参数字段名
//4通过注解获取目标对象的目标字段名
//5通过注解获取目标对象的目标方法
//6目标对象的目标方法通过目标对象和参数值利用反射获取目标对象的结果对象
//7目标字段通过目标对象的结果对象利用反射获取值
//缓存
HashMap<Object, Object> cache = new HashMap<>();
//1获取结果集中的对象的类元信息
Class<?> clazz = collection.iterator().next().getClass();//order类元信息
//2通过类元信息获取该对象的所有字段
Field[] declaredFields = clazz.getDeclaredFields();
//3遍历所有字段
for (Field declaredField : declaredFields) {
//3获取每个字段上的注解
SetValueField annotation = declaredField.getAnnotation(SetValueField.class);
if (annotation == null) continue;
//4设置该有注解的字段可见,为赋值做准备
declaredField.setAccessible(true);
//①通过注解获取目标对象实例
Object bean = this.applicationContext.getBean(annotation.beanClass());
//②通过注解获取目标对象的目标方法名
String methodName = annotation.method();
//③通过注解获取结果集中对象的参数字段名
String paramFieldName = annotation.paramField();
Field paramField = clazz.getDeclaredField(paramFieldName);
paramField.setAccessible(true);
//⑤通过注解获取目标对象的目标字段名
String targetFieldName = annotation.targetField();
Field targetField = null;
//⑥通过注解获取目标对象的目标方法
//Method method = annotation.beanClass().getMethod(methodName, clazz.getDeclaredField(paramName).getType());
Method method = bean.getClass().getMethod(methodName, paramField.getType());
//⑦目标对象的目标方法通过目标对象和参数值利用反射获取目标对象的结果对象
Boolean flag = StringUtils.isEmpty(targetFieldName);
String keyPrefix = annotation.beanClass()+"-"+annotation.method()+"-"+annotation.targetField()+"-";
//5有注解的字段则需要遍历结果集,将每个对象的该字段赋值
for (Object obj : collection) {
Object paramValue = paramField.get(obj);
if (paramValue == null) continue;
//目标对象的结果对象及结果对象的目标字段值
Object resultBean = null;
Object targetFieldValue = null;
String key = keyPrefix+paramValue;
if (cache.containsKey(key)) {
targetFieldValue = cache.get(key);//缓存拿目标对象的结果对象
} else {
resultBean = method.invoke(bean, paramValue);//user类元信息
if (!flag) {
if (resultBean != null) {
if (targetField == null) {
targetField = resultBean.getClass().getDeclaredField(targetFieldName);
targetField.setAccessible(true);
}
targetFieldValue = targetField.get(resultBean);
}
}
cache.put(key,targetFieldValue);
}
Object currentNeedVlaue = declaredField.get(obj);
if (currentNeedVlaue == null){
declaredField.set(obj,targetFieldValue);
}
}
}
}
}
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String name;
}
public class Order implements Serializable {
public static final long serialVersionUID = 1L;
private int id;
private int customId;
//beenClass:反射需要知道的类
//param:反射方法的入参 (因为我们要拿到方法,拿到方法除了知道方法名字外还需要知道方法的入参数类型)
//method:反射执行所需的方法名
//targetFiled:指的是dao层的返回user对象结果中的的所需字段名字,可以通过该名字拿到具体的值
@SetValueField(beanClass = UserMapper.class,paramField = "customId",method = "findUserById",targetField = "name")
private String customName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getCustomId() {
return customId;
}
public void setCustomId(int customId) {
this.customId = customId;
}
public String getCustomName() {
return customName;
}
public void setCustomName(String customName) {
this.customName = customName;
}
}
@Mapper
public interface UserMapper {
@Select("SELECT * FROM s_user WHERE id = #{customId}")
User findUserById(@Param("customId") int customId);
}
@Mapper
public interface OrderMapper {
@Select("SELECT * FROM b_order")
@Results(id = "orderList",value = {
@Result(property = "id",column = "id"),
@Result(property = "customId",column = "custom_id"),
@Result(property = "customName",column = "custom_name"),
})
List<Order> findOrderList();
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User findUserById(Integer id) {
return userMapper.findUserById(id);
}
}
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
@SetValue
public List<Order> findOrderList(int pageNum,int pageSize){
return orderMapper.findOrderList();
}
}
@Test
void contextLoads() {
System.out.println(userService.findUserById(1).getName());
}
@Test
void test() {
List<Order> orderList = orderService.findOrderList(1, 10);
System.out.println(orderList.get(1).getCustomName());
}
结果:只打印了用户名字段
前置增强操作
2020-09-03 11:14:52.361 INFO 13504 — [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
后置增强操作
曹操(用户名)
小结:注解+反射+切面需多加练习,并理解其设计模式