Spring:SpringAop配合自定义注解实现切面编程

此文章只作为笔记记录,不作为讲解文章。

1. SpringAop简介

      传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性问题,这些横切性的问题和我们的主业务逻辑关系不大,这些横切性问题不会影响到主逻辑实现的,但是会散落到代码的各个部分,难以维护。AOP是处理一些横切性问题,AOP的编程思想就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。

      SpringAop的应用场景
  • 日志记录
  • 权限验证
  • 效率检查
  • 事务管理
  • exception

2. 依赖包引入

//SpringBoot项目引入Aop依赖
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.1.5.RELEASE</version>
        </dependency>


//Spring项目引入Aop依赖
        <!-- springAop依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.4.RELEASE</version>
        </dependency>
        <!--  springAop依赖Aspect的语法标准包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>

3. Aop实现示例

项目结构

3.1 定义依赖注入扫描器

AppConfig配置类

package com.java.study.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

//扫描注解
@ComponentScan("com.java.study")
//开启Aop,默认为false(JDK代理模式) true(Cglib模式)
// (this) JDK代理时,指向接口和代理类proxy,cglib代理时 指向接口和子类(不使用proxy)
@EnableAspectJAutoProxy(proxyTargetClass = true) public class AppConfig { }

3.2 自定义service方法

TestService测试方法类

package com.java.study.service;

import org.springframework.stereotype.Component;

@Component("testService")
public class TestService {

    public void Test1(){
        System.out.println("这是测试方法 test1 ......");
    }

}

3.3 定义切面类

TestAdvice切面类

package com.java.study.aspect;

import com.java.study.custom.KthLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.logging.Logger;

@Component
@Aspect
public class TestAdvice {
    private final static Logger logger = Logger.getLogger("TestAdvice.class");

     // @annotation匹配的是自定义注解所标注的方法
    @Pointcut("@annotation(com.java.study.custom.KthLog)")
    public void loggerMother(){}

    @Pointcut("execution(* com.java.study..*())")
    public void loggerMother2(){}

    @Pointcut("execution(* com.java.study.*(java.lang.String))")
    public void loggerMother3(){}

    @Before("loggerMother() && @within(log)")
    public void Before(JoinPoint pointcut, KthLog log){
        System.out.println(" 方法名:"+ pointcut.getSignature().getName() +"日志输出:"+log.value());
    }

    @After("loggerMother2()")
    public void Before(){
        System.out.println(" 测试增强方法 。。。。。。");
    }

    @Around("loggerMother2()&&!loggerMother3()")
    public Object arround(ProceedingJoinPoint joinPoint) {
        logger.info("方法环绕start.....");
        try {
            Object o = joinPoint.proceed();
            logger.info("方法环绕proceed,结果是 :" + o);
            return o;
        } catch (Throwable e) {
            return null;
        }
    }

}
//execution表达式 (用于匹配方法)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
问号 ? 表示当前项可以有也可以没有,其中各项的语义如下:
modifiers-pattern:方法的可见性,如public,protected;(private不能被代理)
ret-type-pattern:方法的返回值类型,如int,void等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;
.. : 表示当前包及其子包
//within表达式 (用于匹配类)
within(declaring-type-pattern)
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
//args表达式 (args匹配的是运行时传递给方法的参数类型) 与 execution 不同
args(java.io.Serializable) //匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配

3.4 定义启动类

TestApp启动类

package com.java.study;

import com.java.study.config.AppConfig;
import com.java.study.service.TestService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestApp {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        TestService testService = (TestService) ac.getBean("testService");
        testService.Test1();
    }
}

4. 自定义注解

定义 KthLog注解类

package com.java.study.custom;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

//元注解 @Retention @Retention(RetentionPolicy.RUNTIME)
public @interface KthLog { String value() default ""; }

元注解讲解

java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解

   @Retention – 什么时候使用该注解

   @Target – 注解用于什么地方   

   @Documented – 注解是否将包含在JavaDoc中   

   @Inherited – 是否允许子类继承该注解

1.)@Retention – 定义该注解的生命周期
  ●   RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
  ●   RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
  ●   RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

2.)@Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括
  ● ElementType.CONSTRUCTOR: 用于描述构造器
  ● ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
  ● ElementType.LOCAL_VARIABLE: 用于描述局部变量
  ● ElementType.METHOD: 用于描述方法
  ● ElementType.PACKAGE: 用于描述包
  ● ElementType.PARAMETER: 用于描述参数
  ● ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明

3.)@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。

4.)@Inherited – 定义该注释和子类的关系
  ● @Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。

5. 配合Aop增强类使用

修改TestService测试方法类

package com.java.study.service;

import com.java.study.custom.KthLog;
import org.springframework.stereotype.Component;

@Component("testService")
public class TestService {

    @KthLog("这是TestService类中******")
    public void Test1(){
        System.out.println("这是测试方法 test1 ......");
    }

}

之后使用TestAdvice切面类的loggerMother方法即可。

----------------------------------- 作者:怒吼的萝卜 链接:http://www.cnblogs.com/nhdlb/ -----------------------------------
原文地址:https://www.cnblogs.com/nhdlb/p/15227384.html