刚参加工作不久,很多基础东西都不懂,希望通过博客一步步积累,本文主要是为了整理一下最近自学的spring aop,自定义注解,反射机制。
项目结构如下:
demo主要功能: 前端请求controller接口上传用户信息,用户信息里面包括加密的信息,利用aop拦截所有controller方法,通过反射机制读取运行时信息进行解密操作,service层对解密后的用户信息进行显示
Controller层-SpringMVC
DemoController,有一个请求处理方法
@RestController
@RequestMapping("/demo")
@Api(description = "demo接口")
public class DemoController{
@Autowired
private DemoService demoService;
@ApiOperation("提交用户信息")
@RequestMapping(value="/uploadUserInfo",method = RequestMethod.POST)
public void submitInfo(@RequestBody UserInfoParam param){
demoService.submitInfo(param);
}
}
方法接受参数为UserInfoParam,定义如下:包括加密字段mobile,且设计为所有包括加密字段的参数类需继承BasicAnnotationParam
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("用户信息参数")
public class UserInfoParam extends BasicAnnotationParam{
/**
* 手机号
*/
@EncryptField
private String mobile;
/**
* 年龄
*/
private Integer age;
}
AOP
由于在Controller接口方法里面,请求参数可能包含加密字段,因此利用aop对Controller所有方法进行拦截。
@Component
@Aspect
@Slf4j
public class EncryptDecryptAop {
@Autowired
private DecryptDomainService decryptDomainService;
//设置切面, 多个切入点字段加解密aop技术,反射机制
@Around("execution(* com.example.demo.controller.DemoController.*(..))")
public Object doProcess(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("方法执行前。。。");
//捕获方法参数列表
List<Object> methodArgs = this.getMethodArgs(proceedingJoinPoint);
//循环所有参数
for (Object item : methodArgs) {
//若参数为BasicAnnotationParam的子类
if (item instanceof BasicAnnotationParam) {
//捕获参数类中的所有字段
Field[] fields = item.getClass().getDeclaredFields();
//遍历所有字段
for (Field field : fields) {
//若该字段被EncryptField注解,则进行加密
if (null != AnnotationUtils.findAnnotation(field, EncryptField.class)) {
//设置private类型允许访问
field.setAccessible(Boolean.TRUE);
field.set(item, decryptDomainService.decryptField(field.get(item)) );
field.setAccessible(Boolean.FALSE);
}
}
}
}
log.info("方法开始执行。。。。");
Object result = proceedingJoinPoint.proceed();
//还可以对返回值result进行解密处理
log.info("方法执行后。。。");
return result;
}
/**
* 获取方法请求参数
*/
private List<Object> getMethodArgs(ProceedingJoinPoint proceedingJoinPoint) {
List<Object> methodArgs = Lists.newArrayList();
for (Object arg : proceedingJoinPoint.getArgs()) {
if (null != arg) {
methodArgs.add(arg);
}
}
return methodArgs;
}
}
@Around(“execution(* com.example.demo.controller.DemoController.*(..))”) 注解标明拦截方式为环绕型,对DemoController下的任何方法进行拦截,可在拦截方法的执行前后进行额外处理。
该注解作用于方法public Object doProcess(ProceedingJoinPoint proceedingJoinPoint)。
关于spring aop的内容可进一步讨论。。。
java反射机制
在上述的AOP拦截方法处理中,判断方法参数中是否包含加密字段,需要采用java反射机制。
首选,通过proceedingJoinPoint.getArgs()
获取方法的参数对象列表methodArgs,并遍历该参数列表。
对所有未继承BasicAnnotationParam类的参数进行忽略,约定此类参数不包含加密字段。
接着,读取参数实例化对象的运行时信息,即对应类的Class对象,有了Class对象对象就可以知道该类的所有运行时信息啦!
比如获取该参数类对所有属性字段
Field[] fields = item.getClass().getDeclaredFields();
再对所有字段信息进行遍历,检查字段是否被EncryptField注解所作用。
对所有EncryptField作用对字段解密操作,
在进行解密的时候,由于属性字段的访问权限一般为private,因此需对该字段临时允许访问,即field.setAccessible(Boolean.TRUE);
,
允许访问后即可对字段值进行解密
field.set(item, decryptDomainService.decryptField(field.get(item)) );
在Field为反射机制相关对类,包括get和set方法,只需传入某个实例化对象即可获得值。
在解密后不要忘了把属性字段对访问权限还原。
field.setAccessible(Boolean.FALSE);
上述解密方法的实现: 简单模拟解密过程,对加密手机号进行反转
@Override
public String decryptField(Object param) {
StringBuilder message = new StringBuilder(param.toString());
return message.reverse().toString();
}
关于反射机制的内容可进一步讨论。。。
自定义注解
上述对注解@EncryptField为自定义注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface EncryptField {
}
该注解主要作用是标明哪些字段已加密,且通过反射机制可读取到。
关于注解的内容可进一步讨论。。。
Service层
对解密后的用户信息进行输出。
@Service
@Slf4j
public class DemoServiceImpl implements DemoService {
/**
* 提交用户信息
*/
@Override
public void submitInfo(UserInfoParam param) {
log.info("正在输出mobile={},age={}", param.getMobile(), param.getAge());
}
}
结果测试
本地启动项目,端口8080,利用命令curl发起http请求
curl -X POST –header “Content-Type: application/json” –header “Accept: /” -d “{\”age\”: 10,\”mobile\”:\”68623226131\”}” “http://127.0.0.1:8080/demo/uploadUserInfo”
允许结果为: