前言
作业系统在测试过程中出现了学生修改路由可以到达教师界面并且可以使用教师功能的问题,学生不用任何工具就可以修改自己的成绩,真的挺要命的,这就要用权限管理进行控制了。
由于没有接触过权限管理,所以一开始也是有点懵,后来应用到实践中,发现也还可以吧。
自定义注解@Admin
如果在实现方式上去描述自定义注解,其实就是接口+注解
package club.yunzhi.workhome.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Admin {
String value() default "";
}
@TARGET
- 用于标注这个注解放在什么地方,类上,方法上,构造器上
- ElementType.METHOD 用于描述方法
- ElementType.FIELD 用于描述成员变量,对象,属性(包括enum实例)
- ElementType.LOCAL_VARIABLE 用于描述局部变量
- ElementType.CONSTRUCTOR 用于描述构造器
- ElementType.PACKAGE 用于描述包
- ElementType.PARAMETER 用于描述参数
- ElementType.TYPE 用于描述类,接口,包括(包括注解类型)或enum声明
@Retention
-
用于说明这个注解的生命周期
- RetentionPolicy.RUNTIME 始终不会丢弃,运行期也保留该注解。因此可以使用反射机制来读取该注解信息。
- 我们自定义的注解通常用这种方式
- RetentionPolicy.CLASS 在类加载的时候丢弃,在字节码文件的处理中有用。注解默认使用这种方式
- RetentionPolicy.SOURCE 在编译阶段丢弃,这些注解在编译结束后就不再有任何意义,所以他们不会写入字节码中
- @Override,@SuppressWarnings都属于这类注解。
- 我们自定义使用中一般使用第一种
- java过程为 编译-加载-运行
@Documented
这样就有了一个自定义注解,但是要想定义它的作用,就需要AOP了。
AOP
基本概念
- AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
- 在不改变原有的逻辑的基础上,增加一些额外的功能。代理也是这个功能,读写分离也能用aop来做。
- AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
- 使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
相关概念
- 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
- Aspect(切面):通常是一个类,里面可以定义切入点和通知
- JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
- Advice(通知):AOP在特定的切入点上执行的增强处理,有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕)
- Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
- weave(织入):将切面应用到目标对象并导致代理对象创建的过程
- introduction(引入):在不修改代码的前提下,引入可以在 运行期 为类动态地添加一些方法或字段
- AOP代理(AOP Proxy):AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
- 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。
通知类型
- Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
- AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
- AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象
- After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式
- Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
由于初次接触AOP,对部分概念还不太理解,也就不展开解释了,以后有机会再写吧。
@Aspect
@Component
public class AdminAspect {
private static final Logger logger = LoggerFactory.getLogger(AdminAspect.class);
@Autowired
WorkService workService;
@Pointcut(value = "@annotation(club.yunzhi.workhome.annotation.Admin)")
public void annotationPointCut() {
}
@Before("annotationPointCut()")
public Object doBefore(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getMethod().getName();
System.out.println("方法名:" + methodName);
if(!validate()){
throw new AccessDeniedException("无操作权限");
}
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
return null;
}
}
private boolean validate(){
System.out.println(this.workService.isTeacher());
return this.workService.isTeacher();
}
}
@Aspect : 将当前类标识为一个切面
@Component :让Spring容器扫描到。
@Pointcut :定义切点
这样一来自定义注解就有了灵魂了,验证到当前角色不是教师,那就抛出异常,否则执行加上注解的方法
以编辑学生为例:
/**
* 更新学生信息
* @param id
* @param student
*/
@PutMapping("{id}")
@Admin
@JsonView(studentJsonView.class)
public void update(@PathVariable Long id, @RequestBody Student student) {
studentService.update(id, student);
}
教师:
学生:
总结:
一开始感觉挺难的,后来实践了才发现还可以,还是不能眼高手低,认为难的不一定难,总会有解决的办法的。
原文
https://segmentfault.com/a/1190000022530800