项目结构
springboot+mybatis+mysql
功能需求
最近领导派了个新活,要做个增删改查。
这个活我拢了拢,有个特点,表单特别多,就是有大量定义好的常量,类似以下这种:
public static Map<Integer,String> RESEARCH_FIELD = new HashMap<Integer,String>(){{
put(0,"经济学");
put(1,"政治学");
put(2,"历史学");
put(3,"社会学");
put(4,"人类学");
put(5,"管理学");
put(6,"公共管理");
put(7,"法学");
put(8,"艺术");
}};
public static Map<Integer,String> EDUCATION = new HashMap<Integer, String>(){{
put(0,"本科在读");
put(1,"学士");
put(2,"硕士在读");
put(3,"硕士");
put(4,"博士在读");
put(5,"博士");
}};
这些常量一般用作前端显示。
以往我对这些常量都是做表存到数据库,需要的时候取出来即可。
但谁让我闲呢,于是试着用aop使用这些常量,顺便也学习一下aop的用法。
代码结构
接口:http://localhost:8080/page
@PostMapping("/page")
@WireDataIdentityAt
public Object page(@RequestParam(required = false, defaultValue = "1") int currentPage,
@RequestParam(required = false, defaultValue = "10") int pageSize) {
try {
List<Paper> papers = paperDao.selectPage(currentPage,pageSize);
return papers;
} catch (Exception e) {
return errorObject(e);
}
}
@WireDataIdentityAt 自定义注解,try catch 方便处理异常
实体类:Paper
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Paper{
private String id;
private String title;
@WireData(AutoConfig.ISO.RESEARCH_FIELD)
private String topicArea;
}
@WireData 自定义注解
AutoConfig.ISO.TOPIC_AREA 自定义枚举类型
自定义注解:@WireDataIdentityAt
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WireDataIdentityAt {
}
@Target 作用目标,METHOD 表示注解可以写到方法上。
@Retention 元注解必须有,详细作用可百度(我就是看百度的)
自定义注解:@WireData
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WireData {
AutoConfig.ISO value() default AutoConfig.ISO.NONE;
}
@Target 作用目标,FIELD 表示注解可以写到字段上。
@Retention 元注解必须有,详细作用可百度(我就是看百度的)
value 类型为自定义枚举,存在默认值。
自定义枚举类型和常量类:AutoConfig
public class AutoConfig {
public enum ISO {
NONE, RESEARCH_FIELD
}
public static Map<Integer,String> RESEARCH_FIELD = new HashMap<Integer,String>(){{
put(0,"经济学");
put(1,"政治学");
put(2,"历史学");
put(3,"社会学");
put(4,"人类学");
put(5,"管理学");
put(6,"公共管理");
put(7,"法学");
put(8,"艺术");
}};
}
一个枚举,一个常量。
aop代码:WireDataAspect
//作用是把当前类标识为一个切面供容器读取
@Aspect
@Component
public class WireDataAspect {
//这个方法在接口执行完返回给前端数据的时候生效。
//@annotation,代表aop拦截WireDataIdentityAt自定义注解所标注的方法。
//returning 代表接口返回的数据。
@AfterReturning(value = "@annotation(com.my.addons.common.annotation.WireDataIdentityAt)", returning = "val")
public void restoreDataAt(Object obj) throws IllegalAccessException {
if (obj != null){
if (obj instanceof ArrayList<?>){
ArrayList<?> list = (ArrayList<?>) obj;
for (Object o : list) {
initDate(o);
}
}else{
initDate(obj);
}
}
}
public void initDate(Object obj) throws IllegalAccessException {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
//判断字段上是否有自定义注解WireData
boolean fieldHasAnno = field.isAnnotationPresent(WireData.class);
//设置私有读写权限,必须设置为true
field.setAccessible(true);
//如果包含自定义注解WireData
if (fieldHasAnno && field.get(obj) != null) {
WireData annotation = field.getAnnotation(WireData.class);
AutoConfig.ISO iso = annotation.value();
switch (iso) {
//--------------------------------------------------------- paper ---------------------------------------------------------//
case RESEARCH_FIELD:
field.set(obj, AutoConfig.RESEARCH_FIELD.get(Integer.parseInt((String) field.get(obj))));
break;
//
}
}
}
}
}
aop实现赋值主要代码在这个方法里,方法用到了反射获取返回数据,然后对数据进行处理
拓展
在使用aop的时候,我发现有时会出现不进aop方法的情况,例如:
接口代码:
@RequestMapping("/addons-bk-user")
public class UserController{
@PostMapping("/export")
public void export(HttpServletResponse response){
List<User> users = mtUserDao.selectList();
doWrite(excelWriter,users,userSheet);
//
}
@WireDataIdentityBf
public void doWrite(ExcelWriter excelWriter, List<?> obj, WriteSheet sheet){
excelWriter.write(obj, sheet);
}
}
@WireDataIdentityBf 自定义注解
aop代码:
@Aspect
@Component
public class WireDataAspect {
@Before(value = "@annotation(com.my.addons.common.annotation.WireDataIdentityBf)")
public void restoreDataBf(JoinPoint joinPoint) throws IllegalAccessException {
Object[] objects = joinPoint.getArgs();
//
}
}
@Before 方法执行前生效
但是代码运行时并没有进入aop方法。
后通过了解得知,aop是对当前类进行了二次包装来实现功能的,所以直接在本类里调用方法不会进入子类aop,可以把doWrite单独放到一个新的类里面,或者干脆自己装配自己也能实现,如下:
@RequestMapping("/addons-bk-user")
public class UserController{
@Autowired
private MyComponent myComponent;
@PostMapping("/export")
public void export(HttpServletResponse response){
List<User> users = mtUserDao.selectList();
myComponent.doWrite(excelWriter,users,userSheet);
//
}
}
@Component
public class MyComponent{
@WireDataIdentityBf
public void doWrite(ExcelWriter excelWriter, List<?> obj, WriteSheet sheet){
excelWriter.write(obj, sheet);
}
}
结尾
主要流程
请求接口 -> 数据库查询数据 -> aop拦截包含自定义注解WireDataIdentityAt的方法 -> 循环字段判断字段上是否包含自定义注解WireData -> 进case对字段重新赋值 -> aop完成返回前端
总结
aop可以做到不动旧代码的情况下做一些特殊的定制修改。