Spring框架系列(五)--SpringAOP以及实现⽤户登录权限控制
⼀、背景:
当需要为多个不具有继承关系的对象引⼊⼀个公共⾏为,例如⽇志、权限验证等功能时。
如果使⽤OOP,需要为每个Bean引⼊这些公共⾏为。会产⽣⼤量重复代码,并且不利⽤维护。
AOP就是为了解决这个问题。
⼆、AOP:
AOP可以理解⼀种思想,不是Java独有的,作⽤是对⽅法进⾏拦截处理或增强处理。⽽在Java中我们使⽤Spring AOP和AspectJ。
1、Spring AOP:
基于动态代理实现,如果⽬标对象有实现接⼝,使⽤jdk proxy,如果⽬标对象没有实现接⼝,使⽤cglib。
然后从容器获取代理后的对象,在运⾏期植⼊“切⾯”类的⽅法。Spring AOP需要依赖于IOC容器来管理,
只能作⽤于Spring容器中的Bean。
Spring AOP可以使⽤注解或者XML配置的⽅式,⽽类似@Aspect、@Pointcut等都是AspectJ的注解,但是通过Spring去实现,只是沿⽤AspectJ的概念。
通常Spring AOP⾜够⽇常开发,⼀般使⽤不到AspectJ。
Spring AOP在运⾏时⽣成代理对象来织⼊的,还可以在编译期、类加载期织⼊,⽐如AspectJ。
2、AspectJ:
AspectJ在实际代码运⾏前完成了织⼊,所以它⽣成的类是没有额外运⾏时开销的。
⽽Spring AOP基于动态代理,⽣成⼀个代理类,这样栈深度更深,效率理论上要差于AspectJ。
AspectJ功能更强⼤,是AOP的完整解决⽅案。
AspectJ除了注解,个⼈不太了解,这⾥就不细讲了。
三、Spring AOP应⽤:
1、参数校验。
2、MySQL读写分离。
3、权限校验。
4、⽇志记录。
5、信息过滤,页⾯转发等功能。
四、Spring AOP术语:
在⼀个或多个连接点上,可以把切⾯的功能(通知)织⼊到程序的执⾏过程中
1、增强Advice:
⽅法层⾯的增强。对某个⽅法进⾏增强的⽅法,分为:Before、After、After-returning、After-throwing、Around。
try {
//@Before
result = method.invoke(target, args);
//@After
return result;
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
//@AfterThrowing
throw targetException;
} finally {
//@AfterReturning
}
2、连接点Join Point:
可以被拦截到的点。也就是可以被增强的⽅法都是连接点。
3、切点Pointcut:
joint point的组合,通常使⽤类和⽅法名称或者正则表达式匹配来指定切点。
4、切⾯Aspect:
切⾯是Advice和Pointcut的结合,他们共同定义了在何时和何处完成其功能。
5、引⼊Introduction:
向现有的类添加新⽅法或属性。
6、织⼊Weaving:
把切⾯应⽤到⽬标对象并创建新的代理对象的过程。切⾯在指定的连接点被织⼊到⽬标对象中。
在⽬标对象的⽣命⾥有多个点可以进⾏织⼊:编译器、类加载期、运⾏期。
7、⽬标对象Target:
织⼊ Advice 的⽬标对象。
Spring对AOP的⽀持:由于基于动态代理实现,所以只⽀持⽅法级别的连接点。
1、基于代理的经典Spring AOP。
2、纯POJO切⾯。
3、@AspectJ注解驱动的切⾯。
4、注⼊式AspectJ切⾯(适⽤于Spring各版本)。
五、Spring AOP实现:
1、添加Maven依赖:
  1).aspectjweaver
  2).如果使⽤Spring Boot:spring-boot-starter-aop
2、⾸先开启Spring AOP⽀持:
XML⽅式:<aop:aspectj-autoproxy/>
注解⽅式:@EnableAspectJAutoProxy,所有被@aspect配置的Bean,都是Aspect
3、基于XML(schema-based)
Spring AOP要使⽤AspectJ的切点表达式定义切点:
@execution:上⾯使⽤了execution来正则匹配⽅法,是最常⽤的。也可以使⽤其他的指⽰器。
execution表达式以*开始,表⽰不关⼼⽅法返回值的类型,两个点号(..)表名切点要选择任意的perform()⽅法,⽆论⽅法的参数是什么。
@within:指定所在类或所在包下⾯的⽅法
例如:@Pointcut("within(com.it.aop.AService..*)")
@annotation:⽅法上具有特定的注解,如@Subscribe⽤于订阅特定的事件。
例如:@Pointcut("execution(* .(..)) && @annotation(com.javadoop.annotation.Subscribe)")
@bean:匹配bean的名字
例如:@Pointcut("bean(*Service)")
PS:通常 "." 代表⼀个包名,".." 代表包及其⼦包,⽅法参数任意匹配使⽤两个点 ".."。
举个栗⼦:
public class AService {
public void add() {
System.out.println("add");
}
}
public class LogRecord {
public void log() {
System.out.println("record log");
}
public void transaction() {
System.out.println("transaction");
}
public void permission() {
System.out.println("permission");
}
}
<aop:config>    <!--顶层的AOP配置元素。⼤多数aop元素都在这内部 -->
<!--声明⼀个切⾯ -->
<aop:aspect ref="logRecord">
<!--前置通知 -->
<aop:before pointcut="execution(** com.it.aop.AService.add(..))" method="log" />
<aop:before pointcut="execution(** com.it.aop.AService.add(..))" method="permission" />
<!--返回通知 -->
<aop:after-returning pointcut="execution(** com.it.aop.AService.add(..))" method="log" />
<!--异常通知 -->
<aop:after-throwing pointcut="execution(** com.it.aop.AService.add(..))" method="transaction" />
</aop:aspect>
</aop:config>
上述代码中,Pointcut都是相同的我们就可以声明<aop:pointcut>,如果把<aop:pointcut>作为<aop:config>的直接⼦元素,将作为全局Pointcut <aop:config>    <!--顶层的AOP配置元素。⼤多数aop元素都在这内部 -->
<!--声明⼀个切⾯ -->
<aop:aspect ref="logRecord">
<aop:pointcut id="add" expression="execution(** com.it.aop.AService.add(..))" />
<aop:before pointcut-ref="add" method="log" />
</aop:aspect>
</aop:config>
环绕通知
public void around(MethodInvocationProceedingJoinPoint point) {
try {
System.out.println("record log");
System.out.println("permission");
point.proceed();
System.out.println("record log");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
<aop:config>    <!--顶层的AOP配置元素。⼤多数aop元素都在这内部 -->
<!--声明⼀个切⾯ -->
<aop:aspect ref="logRecord">
<aop:pointcut id="add" expression="execution(** com.it.aop.AService.add(..))" />
<!--环绕通知 -->
<aop:around pointcut-ref="add" method="around" />
</aop:aspect>
</aop:config>
4、基于注解(@AspectJ)实现:
@AspectJ和AspectJ没多⼤关系,仅仅是使⽤了AspectJ中的概念,注解来⾃于AspectJ的包,但是实现还是Spring AOP来的。
@Aspect
public class LogRecord {
@Pointcut("execution(** com.it.aop.AService.add(..))")
public void add() {}
@Before("add()")
public void log() {
System.out.println("record log");
}
@AfterThrowing("add()")
public void transaction() {
System.out.println("transaction");
}
@Before("add()")
spring系列框架有哪些public void permission() {
System.out.println("permission");
}
@Around("add()")
public void around(MethodInvocationProceedingJoinPoint point) {
try {
System.out.println("record log");
System.out.println("permission");
point.proceed();
System.out.println("record log");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
PS:Spring通常建议创建⼀个SystemArchitecture类,⾥⾯定义Pointcut,然后在需要的地⽅去引⽤。例如,在@Aspect的Bean中使⽤@Before("com.it.SystemArchitecture.A")
Spring AOP通过@annotation实现权限控制
PS:这⾥只是校验部分API的登录状态和⽤户权限,如果系统要求登录过后才能请求,肯定就选择了。
1、⾸先定义两个注解
//@CheckLogin通过Cookie是否包含X-Token验证⽤户是否登录
public @interface CheckLogin {
}
//@CheckAuthorization("**")校验⽤户是否登录,权限**是否满⾜
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckAuthorization {
String value();
}
2、注解的AOP处理
/* *
* Description: 通过校验jwt中token实现登录和⽤户权限校验
**/
@Aspect        //定义为⼀个切⾯
@Component    //必须声明为Bean
@RequiredArgsConstructor(onConstructor = @__(@Autowired))    //lombok实现IOC,相⽐直接通过@Autowired更有优势public class AuthAspect {
private final JwtOperator jwtOperator;    //jwt操作类
//@CheckLogin注解操作
@Around("@annotation(com.diamondshine.auth.CheckLogin)")
public Object checkLogin(ProceedingJoinPoint point) throws Throwable {
checkToken();
return point.proceed();
}
private void checkToken() {
try {
// 1. 从header⾥⾯获取token
HttpServletRequest request = getHttpServletRequest();
Cookie[] cookies = Cookies();
String token = "";
for (Cookie cookie : cookies) {
if (StringUtils.equals("X-Token", Name())) {
token = Value();
break;
}
}
// 2. 校验token是否合法&是否过期;如果不合法或已过期直接抛异常;如果合法放⾏
Boolean isValid = jwtOperator.validateToken(token);
if (!isValid) {
throw new SecurityException("Token不合法!");
}
// 3. 如果校验成功,那么就将⽤户的信息设置到request的attribute⾥⾯
Claims claims = ClaimsFromToken(token);
request.setAttribute("id", Long.("id").toString()));
request.setAttribute("role", ("role"));
} catch (Throwable throwable) {
throw new SecurityException("Token不合法!");
}
}
private HttpServletRequest getHttpServletRequest() {
RequestAttributes requestAttributes = RequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
Request();
}
/
/@CheckAuthorization注解操作
@Around("@annotation(com.diamondshine.auth.CheckAuthorization)")
public Object checkAuthorization(ProceedingJoinPoint point) throws Throwable {
try {
// 1. 验证token是否合法;
this.checkToken();
// 2. 验证⽤户⾓⾊是否匹配
HttpServletRequest request = getHttpServletRequest();
List<String> list = (List<String>) Attribute("role");
MethodSignature signature = (MethodSignature) Signature();
Method method = Method();
/
/获取@CheckAuthorization注解值
CheckAuthorization annotation = Annotation(CheckAuthorization.class);
String value = annotation.value();
//ROLE_ADMIN,⽤户权限必须为admin。如果注解为ROLE_ADMIN_USER,⽤户权限为admin/user都可以
list.forEach(role -> {
if (!(("ROLE_ADMIN".equals(value) && "ROLE_ADMIN".contains(role)) || ("ROLE_ADMIN_USER".equals(value) && "ROLE_ADMIN_USE R".contains(role)))) {
throw new SecurityException("⽤户⽆权访问!");
}
});
} catch (Throwable throwable) {
if (StringUtils.Message())) {
throw new Message(), throwable);
} else {
throw new SecurityException("⽤户⽆权访问!", throwable);
}
}
return point.proceed();
}
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。