使用Spring AOP和自定义注解记录日志

什么是注解?

      对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
  Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。


什么是AOP?

    它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

AOP相关概念

切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。

连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上

引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口

 

目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO

AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。


我今天要做的就是利用以上这两样东西,实现在每个有某自定义注解的方法被调用时,都去打印操作日志。


自定义注解:

自定义注解类编写的一些规则:
  1. Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
  2. 参数成员只能用public或默认(default)这两个访问权修饰
  3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
  4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
  5. 注解也可以没有定义成员, 不过这样注解就没啥用了
PS:自定义注解需要使用到元注解


元注解:

作用:负责注解其他注解(可用于自定义注解)

1、@Retention

作用:标示注解在什么时候可见(运行时可见、仅在.class文件及源代码中可见、仅在源代码中可见),value可用参数有:

属性值作用
RetentionPolicy.RUNTIME标示该注解可以再运行时通过反射找到(ORM框架许多注解使用了该参数)
RetentionPolicy.CLASS标示该注解保存在.class文件中,但在运行时不能通过反射找到
RetentionPolicy.SOURSE标示该注解只在源码中可见

2、@Target

作用:标示该注解用于注解什么元素(类、方法、变量等),value可用参数有:

属性值作用
ElementType.PACKAGE标示该注解用于注解包声明
ElementType.ANNOTATION_TYPE标示该注解用于注解其他注解
ElementType.CONSTRUCTOR标示该注解用于注解构造函数
ElementType.FIELD标示该注解用于注解成员变量
ElementType.METHOD标示该注解用于注解方法
ElementType.TYPE标示该注解用于注解类,接口,枚举类型
ElementType.PARAMETER标示该注解用于注解参数
ElementType.LOCAL_VARIABLE标示该注解用于注解局部变量

3、@Inherited

作用:指定被它修饰的注解具有继承性(比如InheritedAnnotation注解了BaseClass,SubClass继承了BaseClass,这时SubClass也会具有InheritedAnnotation注解,但方法跟参数等的注解不会被继承)

4、@Documented

作用:表明被注解元素应该加入到JavaDoc(文档)


下面步入正题,开始编码

1、注解定义:

 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {
    /** 操作类型(INSERT、UPDATE、SELECT、DELETE)*/
    public String optType();
    /** 描述 */
    public String describe();
    /** 模块 */
    public String module();
}

2、定义一个切面,并定义方法返回时和方法报错时的处理逻辑:

@Aspect
@Component(value = "loggerAspect")
public class LoggerAspect {

    private Logger logger = LoggerFactory.getLogger(LoggerAspect.class);

    @Pointcut("execution(public * com.wb.wbao.web.*.*(..)) && @annotation(com.wb.wbao.common.annotation.Loggable)")
    public void log(){
    }

    @AfterReturning(value = "log()", returning = "retVal")
    public void log(JoinPoint joinPoint, Object retVal) {
        // 获取参数
        Object[] params = joinPoint.getArgs();
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        Class<?> targetClass = joinPoint.getTarget().getClass();
        Method method = null;
        for (Method mt : targetClass.getMethods()) {
            if (methodName.equals(mt.getName())) {
                method = mt;
                break;
            }
        }
        Loggable loggable = method.getAnnotation(Loggable.class);
        if(Objects.isNull(loggable)){
            return;
        }
        logger.info("loggable desc:{}, optType:{}, module:{},params:{}", loggable.describe(), loggable.optType(), loggable.module(), params);
        //loggable desc:登录, optType:POST, module:LOGIN,params:[User{loginName='wangbao', password='wangbao'}
    }

    @AfterThrowing(value = "log()", throwing = "ex")
    public void log(JoinPoint joinPoint, Throwable ex) {
        // 获取参数
        Object[] params = joinPoint.getArgs();
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        Class<?> targetClass = joinPoint.getTarget().getClass();
        Method method = null;
        for (Method mt : targetClass.getMethods()) {
            if (methodName.equals(mt.getName())) {
                method = mt;
                break;
            }
        }
        Loggable loggable = method.getAnnotation(Loggable.class);
        if(Objects.isNull(loggable)){
            return;
        }

        logger.info("loggable desc:{}, optType:{}, module:{}, exception:{}, params:{}", loggable.describe(), loggable.optType(), loggable.module(), ex.getMessage(),  params);
        //loggable desc:登录, optType:POST, module:LOGIN, exception:/ by zero, params:[User{loginName='wangbao', password='wangbao'}
    }
}

3、实际使用该注解:

    @Loggable(describe = "登录", optType = "POST", module = "LOGIN")
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ResponseBody
    public CommonDTO login(@RequestBody User user) {...}

启动项目,登录系统,如果登录成功则看到日志:

如果登录失败(被调用的方法抛出异常),则看到日志:



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 可以使用Spring AOP来通过自定义注解来实现切入点。首先,定义一个自定义注解,并在需要切入的方法上使用注解,然后定义一个切面,并在切面中使用@Before 或 @Around 注解来拦截被标记的方法,最后,在Spring的配置文件中声明所需的切面,此时,所有被标记的方法都会被拦截。 ### 回答2: 使用Spring AOP切入自定义注解有以下几个步骤: 1. 在项目中引入Spring AOP的依赖,一般为spring-aopspring-aspects。 2. 在配置文件中开启Spring AOP的自动代理功能。可以通过在XML配置文件中添加<aop:aspectj-autoproxy />或在Java配置文件中添加@EnableAspectJAutoProxy注解实现。 3. 创建一个切面类,用于定义切入的逻辑。这个类需要使用@Component或者其他Spring注解进行标识,以便Spring能够扫描到。 4. 在切面类的方法中,使用@Before、@After等注解定义切入点和具体的切入操作。例如,使用@Before注解定义在某个注解标记的方法执行之前切入的逻辑。 5. 在注解中定义自定义的切点。可以使用@Retention和@Target等元注解来配置注解的生命周期和使用范围。 6. 在目标类或方法上添加自定义的注解。例如,在一个Service类的某个方法上添加自定义注解。 7. 运行项目,Spring会根据配置自动代理目标类,当目标类或方法被调用时,切面类中定义的切入逻辑就会自动被执行。 8. 可以通过配置切入的顺序、通知的类型等来进一步细化切入的逻辑。 通过以上步骤,我们可以使用Spring AOP方便地切入自定义注解,实现对目标类或方法的增强、日志记录、权限控制等功能。 ### 回答3: 使用Spring AOP切入自定义注解需要以下几个步骤: 1. 定义一个自定义的注解。可以使用Java提供的`@interface`关键字创建一个注解,例如: ```java @Target(ElementType.METHOD) // 定义注解的作用范围为方法 @Retention(RetentionPolicy.RUNTIME) // 注解在运行时可见 public @interface CustomAnnotation { // 自定义注解的属性 } ``` 2. 创建一个切面类来处理注解。可以使用Spring提供的`@Aspect`注解来标记切面类,并在方法上使用`@Before`、`@After`等注解来定义切入点和增强逻辑。例如: ```java @Aspect @Component public class CustomAspect { @Before("@annotation(customAnnotation)") // 拦截带有CustomAnnotation注解的方法 public void beforeMethod(CustomAnnotation customAnnotation) { // 在方法执行前执行的逻辑 } } ``` 3. 配置Spring AOP。在Spring配置文件中添加AOP的配置,例如使用`<aop:aspectj-autoproxy>`标签开启自动代理,并指定切面类的包名,让Spring能够自动扫描并应用切面逻辑。 4. 在目标方法上使用自定义注解。在需要切入的方法上标记使用自定义注解,例如: ```java @CustomAnnotation public void doSomething() { // 方法的实际逻辑 } ``` 这样,在调用`doSomething()`方法时,Spring AOP会拦截到带有`@CustomAnnotation`注解的方法,并执行切面逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值