Spring自定义注解的使用(实现切面aspect)
·
AOP的基本概念
- Aspect(切面):通常是一个类,里面可以定义切入点和通知(切面 = 切点+通知)
- Pointcut(切点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
- JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
- Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
- AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
连接点和切入点的区别:
- Jointpoint(连接点) 是具体的某个目标方法
- Pointcut(切入点) 用于指定 “连接点” 的一个表达式
通知类型
- Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
- AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
- AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象
- After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式
- Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
通知After和AfterReturning
- After无论是否异常都一定执行,AfterReturning无异常才执行
1:自定义RequestMapBody 注解
package com.xs.breast.commons.framework.web.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestMapBody {
String operationType() default "";
}
这里注解类上的三个注解称为元注解,其分别代表的含义如下:
- @Target:注解作用的位置,ElementType.PARAMETER表示该注解仅能作用于参数上,ElementType.METHOD表示该注解仅能作用于方法上,ElementType.TYPE表示该注解仅能作用于类上。作用在参数或者方法上@Target(ElementType.PARAMETER,ElementType.METHOD)
- @Retention:注解的生命周期,表示注解会被保留到什么阶段,可以选择编译阶段、类加载阶段,或运行阶段
保留的时间范围 (RetentionPolicy)
SOURCE源文件保留(如@Override保留在源文件,编译后注解消失)
CLASS编译时保留(如lombok生成get/set)
RUNTIME运行时保留(如切面记录日志,或验证参数信息等)- @Documented:注解信息会被添加到Java文档中
- @Inherited:子类注解自动继承该注解(更加业务情况选择)
注意:创建这样一个注解,仅仅是一个标志,装饰类、方法、属性的,并没有功能,要想实现功能,需要我们通过拦截器、AOP切面这些地方获取注解标志,然后实现我们的功能
java自定义注解的使用范围
一般我们可以通过注解来实现一些重复的逻辑,就像封装了的一个方法,可以用在一些权限校验、字段校验、字段属性注入、保存日志、缓存
2:创建一个AOP切面类来拦截@RequestMapBody这个注解
@Aspect
@Component
@Slf4j
public class RequestMapBodyAop{
//@Pointcut("execution(* com.xs.breast.commons.framework.web.annotation.RequestMapBody)")
@Pointcut("@annotation(com.xs.breast.commons.framework.web.annotation.RequestMapBody)")
private void pointcutOne(){
}
//@Before("pointcutOne() && @annotation(content) ")
//@After("pointcutOne()")
//@AfterRunning("pointcutOne()")
//@AfterThrowing("pointcutOne()")
@Around("pointcutOne()")
public Object aroud(ProceedingJoinPoint joinPoint) throws Throwable {
//获取参数,可对参数进行操作
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if(args[i] instanceof String) {
args[i] = args[i] + "---";
break;
}
Object result = null;
//对返回数据进行操作
Object result = joinPoint.proceed(args);
if(result instanceof String) {
String content =result.toString();
return content;
}
return result;
log.info("请求参数:{}",JSON.toJSON(args));
log.info("返回结果:{}",JSON.toJSON(result));
return result;
}
}
- @Pointcut声明了切点(这里的切点是我们自定义的注解类),切点可以指定到包路径下的类或方法
- @Before中的@annotation(content) 可以获取自定义的注解对象,所以就能够获取我们在使用注解时赋予的值了
( public Object aroud(ProceedingJoinPoint joinPoint,RequestMapBody content) throws Throwable {)- JoinPoint能做的ProceedingJoinPoint都能做,ProceedingJoinPoint继承了JoinPoint接口
@Before("pointcutOne() && @annotation(content)")
public void advice(JoinPoint joinPoint, RequestMapBody content) {
System.out.println("注解作用的方法名: " + joinPoint.getSignature().getName());
System.out.println("所在类的简单类名: " + joinPoint.getSignature().getDeclaringType().getSimpleName());
System.out.println("所在类的完整类名: " + joinPoint.getSignature().getDeclaringType());
System.out.println("目标方法的声明类型: " + Modifier.toString(joinPoint.getSignature().getModifiers()));
}
3:使用注解
@RestController
@RequestMapping("/demo")
public class DemoController {
@RequestMapBody()
@GetMapping("/getContent")
public Object getContent( String content) {
return content;
}
}
4:示例:角色校验注解(springsecurity中的角色校验)
我们在使用springsecurity有一个注解@PreAuthorize可以作用在类或方法上,用来校验是否有权限访问,我们可以模仿这个注解,写一个我们自定义注解来实现同样的功能
- 创建一个自定义角色校验注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RoleAuthorize {
String[] value() default {};
}
- 创建一个拦截器
这个拦截器拦截所有访问路径的url,如果访问方法上带有我们创建的自定义注解RoleAuthorize ,则获取这个注解上限定的访问角色,方法没有注解再获取这个类是否有这个注解,如果这个类也没有注解,则这个类的访问没有角色限制,放行,如果有则校验当前用户的springsecurity是否有这个角色,有则放行,没有则抛出和springsecurity一样的异常AccessDeniedException,全局异常捕获这个异常,返回状态码403(表示没有权限访问)
@Component
public class RoleInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod)handler;
//在方法上寻找注解
RoleAuthorize permission = handlerMethod.getMethodAnnotation(RoleAuthorize.class);
if (permission == null) {
//方法不存在则在类上寻找注解则在类上寻找注解
permission = handlerMethod.getBeanType().getAnnotation(RoleAuthorize.class);
}
//如果没有添加权限注解则直接跳过允许访问
if (permission == null) {
return true;
}
//获取注解中的值
String[] validateRoles = permission.value();
//校验是否含有对应的角色
for(String role : validateRoles){
//从springsecurity的上下文获取用户角色是否存在当前的角色名称
if(AuthUserUtils.hasRole("ROLE_"+role)){
return true;
}
}
throw new AccessDeniedException("没有权限访问当前接口");
}
}
- java配置拦截器注册
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Autowired
private RoleInterceptor roleInterceptor;
/**
* 添加拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(roleInterceptor).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
备注:
1.这里添加拦截器可以继承WebMvcConfigurerAdapter (已过时,在springboot2.0是继承 WebMvcConfigurationSupport或实现WebMvcConfigurer)
2.WebMvcConfigurationSupport–>不需要返回逻辑视图,可以选择继承此类.WebMvcCofigurer–>返回逻辑视图,可以选择实现此方法,重写addInterceptor方法
3.继承webmvcconfigurationsupport之后就没有springmvc的自动配置了 建议实现WebMvcConfigurer
- springmvc.xml配置拦截器注册
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 配置自动扫描的包controller才生效 -->
<context:component-scan base-package="com.xs.breast.view"></context:component-scan>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<!-- 定义在mvc:interceptor下面的表示是对特定的请求才进行拦截的 -->
<bean
class="com.xs.breast.commons.framework.interceptor.RoleInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
</beans>
引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
更多推荐
已为社区贡献12条内容
所有评论(0)