自定义注解 & SpringAop 切点切面使用

201 阅读4分钟
  • 自定义注解

    一、 在字段上使用

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {

    String description();

    int length();
}

具体注解的使用

public class MyFieldTest {

    @MyField(description = "username", length = 18)
    private String username;

    private Integer age;

    public static void main(String[] args) {

        Class<MyFieldTest> c = MyFieldTest.class;

        Field[] declaredFields = c.getDeclaredFields();
        for (Field f :
                declaredFields) {

            if (f.isAnnotationPresent(MyField.class)) {
                MyField annotation = f.getAnnotation(MyField.class);
                System.out.println("字段:" + f.getName() + ", annotation description: " + annotation.description() + "length: " + annotation.length());

            }
        }
    }
}

二、在方法上使用

1, 设置注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {


}

2,设置拦截器

public class AnnoInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("进入拦截器了");
        // 反射获取方法上的LoginRequred注解
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
        if(loginRequired==null){
            return true;
        }
        // 有LoginRequired注解说明需要登录,提示用户登录
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().print("你访问的资源需要登录");

        return false;
    }
}

3, 拦截器注册

@Configuration
public class InterceptorTrainConfigurer implements WebMvcConfigurer {


   /**
    * addPathPatterns("/**") 表示拦截所有的请求,
    * addPathPatterns("/**") 表示拦截所有的请求,
    * addPathPatterns("/test/**") 表示拦截/test/ 下的所有路径请求,
    * addPathPatterns("/test/*") 表示拦截/test/abc,拦截/test/aaa , 不拦截 /test/abc/def
    * addPathPatterns("/test/**").excludePathPatterns("/test/login", “/test/register”) 表示拦截/test/ 下的所有路径请求,但不拦截 /test/login 和 /test/registe
    * @param registry
    */
   @Override
   public  void addInterceptors(InterceptorRegistry registry){
       registry.addInterceptor(new AnnoInterceptor()).addPathPatterns("/**");

   }

}

4,Controleler设置

@GetMapping(value = "selectB")
@LoginRequired(username = "zshuai")
public String selectLogin(){


    return "你正在访问BB资源";
}

@GetMapping(value = "selectA")
public String selectA(){

    return "你正在访问A资源";
}

三、自定义注解在切点、切面上的使用

代码样例:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAop {

}

--------------------

@Aspect
@Component
@Slf4j
public class LogAspectMyLog {

    //定义一个切点
    @Pointcut("@annotation(com.ryan.test.LogAop)")
    private void pointCutMethod(){

    }

    //织入环绕通知
    @Around("pointCutMethod()")
    public Object logAround1(ProceedingJoinPoint pjp) throws Throwable {
        log.info("-----------------------------------");
        log.info("环绕通知,进入方法,logAround1");
        Object o = pjp.proceed();
        log.info("环绕通知,退出方法,logAround1");
        return o;
    }


    //织入环绕通知
    @Around("pointCutMethod()")
    public void logAround2(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取方法名称
        String name = joinPoint.getSignature().getName();
        //获取入参
        Object[] param = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder();
        for (Object o :
                param) {
            sb.append(o+";");
        }
        log.info("进入方法:"+name+", 参数为:"+sb.toString());
        //继续执行方法
        Object proceed = joinPoint.proceed();
        log.info(name +"方法执行结束");
    }
}

四、 在类上使用

代码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MessageType {
  int type() default 100;
}

----------LogMessage---------
@MessageType(type = 10)
@Component
@Data
public class LogMessage {

  /**
   * 模块编号
   */
  private String moduleName;

  /**
   * 行为编号
   */
  private String actionName;
}
--------------测试类----------

@Test
void messageTypeTest2() {
  log.info("scan consumeer...");
  LogMessage logMessage = new LogMessage();
  logMessage.setActionName("logAction");
  logMessage.setModuleName("logModule");
  MessageType annotation = logMessage.getClass().getAnnotation(MessageType.class);
  log.info("类型:"+annotation.type());
}


======扩展==

拦截器模式

SpringAop

 - 参考:https://pdai.tech/md/spring/spring-x-framework-aop.html
 - 切点
 - 切面
 - 通知
 通知的执行殊勋

SpringAop 代码

public interface IJdkProxyService {

    void doMethod1();

    String doMethod2();

    String doMethod3() throws Exception;
}

-------------

@Service
@Slf4j
public class IJdkProxyServiceImpl implements IJdkProxyService {
    @Override
    public void doMethod1() {
        log.info("JdkProxyServiceImpl.doMethod1()");
    }

    @Override
    public String doMethod2() {
        log.info("JdkProxyServiceImpl.doMethod2()");
        return "hello world";
    }

    @Override
    public String doMethod3() throws Exception {
        log.info("JdkProxyServiceImpl.doMethod3()");
        throw new Exception("some exception");

    }
}
-----------------

@EnableAspectJAutoProxy
@Component
@Aspect
@Slf4j
public class LogAspect {

    /**
     * 定义切点*
     */
    @Pointcut("execution( * com.tuling.dynamic.datasource.aop.*.*(..) )")
    private void pointCutMethod(){

    }

    @Around("pointCutMethod()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        log.info("-----------------------------------");
        log.info("环绕通知,进入方法");
        Object o = pjp.proceed();
        log.info("环绕通知,退出方法");
        return o;
    }

    @Before("pointCutMethod()")
    public void doBefore(){
        log.info("前置通知");
    }

    /**
     *后置通知
     * @param result
     */
    @AfterReturning( pointcut="pointCutMethod()",returning="result")
    public void doAfterReturing(String result){
        log.info("后置通知"+result);
    }

    /**
     * 异常通知*
     */
    @AfterThrowing( pointcut="pointCutMethod()",throwing = "e")
    public void doAfterThrowing(Exception e){
        log.info("异常通知"+e.getMessage());

    }

    /**最终通知
     * *
     */
    @After("pointCutMethod()")
    public void aoAfter(){
        log.info("最终通知");

    }

}
---------------测试代码--------
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
class IJdkProxyServiceImplTest {

    @Autowired
    private IJdkProxyService iJdkProxyService;

    @Test
    void doMethod1() {
        iJdkProxyService.doMethod1();
    }

    @Test
    void doMethod2() {
        iJdkProxyService.doMethod2();
    }

    @Test
    void doMethod3() throws Exception {
        iJdkProxyService.doMethod3();
    }
}
--------执行明细---------
2023-01-29 11:07:44.521  INFO 4376 --- [           main] c.t.d.datasource.cglib.LogAspectCglib    : -----------------------------------
2023-01-29 11:07:44.521  INFO 4376 --- [           main] c.t.d.datasource.cglib.LogAspectCglib    : 环绕通知,进入方法
2023-01-29 11:07:44.521  INFO 4376 --- [           main] c.t.d.datasource.cglib.LogAspectCglib    : 前置通知
JdkProxyServiceImpl.doMethod2()
2023-01-29 11:07:44.530  INFO 4376 --- [           main] c.t.d.d.cglib.CglibProxyServiceImpl      : JdkProxyServiceImpl.doMethod2()
2023-01-29 11:07:44.530  INFO 4376 --- [           main] c.t.d.datasource.cglib.LogAspectCglib    : 后置通知hello world
2023-01-29 11:07:44.530  INFO 4376 --- [           main] c.t.d.datasource.cglib.LogAspectCglib    : 最终通知
2023-01-29 11:07:44.530  INFO 4376 --- [           main] c.t.d.datasource.cglib.LogAspectCglib    : 环绕通知,退出方法
2023-01-29 11:07:44.561  INFO 4376 --- [ionShutdownHook] com.alibaba.druid.pool.DruidDataSource   : {dataSource-0} closing ...
2023-01-29 11:07:44.561  INFO 4376 --- [ionShutdownHook] com.alibaba.druid.pool.DruidDataSource   : {dataSource-0} closing ...

Process finished with exit code 0


切入点的神明规则

image.png

通知的执行顺序,使用@Order指定
如果有多个通知想要在同一连接点运行会发生什么?Spring AOP遵循跟AspectJ一样的优先规则来确定通知执行的顺序。 在“进入”连接点的情况下,最高优先级的通知会先执行(所以给定的两个前置通知中,优先级高的那个会先执行)。 在“退出”连接点的情况下,最高优先级的通知会最后执行。(所以给定的两个后置通知中, 优先级高的那个会第二个执行)。

当定义在不同的切面里的两个通知都需要在一个相同的连接点中运行, 那么除非你指定,否则执行的顺序是未知的。你可以通过指定优先级来控制执行顺序。 在标准的Spring方法中可以在切面类中实现org.springframework.core.Ordered 接口或者用****Order注解**做到这一点。在两个切面中, Ordered.getValue()方法返回值(或者注解值)较低的那个有更高的优先级**。

当定义在相同的切面里的两个通知都需要在一个相同的连接点中运行, 执行的顺序是未知的(因为这里没有方法通过反射javac编译的类来获取声明顺序)。 考虑在每个切面类中按连接点压缩这些通知方法到一个通知方法,或者重构通知的片段到各自的切面类中 - 它能在切面级别进行排序。

参考