SpringBoot 通过自定义注解实现AOP切面编程实例

一直心心念的想写一篇关于AOP切面实例的博文,拖更了许久之后,今天终于着手下笔将其完成。

基础概念

1、切面(Aspect)

首先要理解‘切’字,需要把对象想象成一个立方体,传统的面向对象变成思维,类定义完成之后(封装)。每次实例化一个对象,对类定义中的成员变量赋值,就相当于对这个立方体进行了一个定义,定义完成之后,那个对象就在那里,不卑不亢,不悲不喜,等着被使用,等着被回收。

面向切面编程则是指,对于一个我们已经封装好的类,我们可以在编译期间或在运行期间,对其进行切割,把立方体切开,在原有的方法里面添加(织入)一些新的代码,对原有的方法代码进行一次增强处理。而那些增强部分的代码,就被称之为切面,如下面代码实例中的通用日志处理代码,常见的还有事务处理、权限认证等等。

2、切入点(PointCut)

要对哪些类中的哪些方法进行增强,进行切割,指的是被增强的方法。即要切哪些东西。

3、连接点(JoinPoint)

我们知道了要切哪些方法后,剩下的就是什么时候切,在原方法的哪一个执行阶段加入增加代码,这个就是连接点。如方法调用前,方法调用后,发生异常时等等。

4、通知(Advice)

通知被织入方法,改如何被增强。定义切面的具体实现。那么这里面就涉及到一个问题,空间(切哪里)和时间(什么时候切,在何时加入增加代码),空间我们已经知道了就是切入点中定义的方法,而什么时候切,则是连接点的概念,如下面实例中,通用日志处理(切面),@Pointcut规则中指明的方法即为切入点,@Before、@After是连接点,而下面的代码就是对应通知。

1

2

3

4

@Before("cutMethod()")

public void begin() {

    System.out.println("==@Before== lingyejun blog logger : begin");

}

5、目标对象(Target Object)

被一个或多个切面所通知的对象,即为目标对象。

6、AOP代理对象(AOP Proxy Object)

AOP代理是AOP框架所生成的对象,该对象是目标对象的代理对象。代理对象能够在目标对象的基础上,在相应的连接点上调用通知。

7、织入(Weaving)

将切面切入到目标方法之中,使目标方法得到增强的过程被称之为织入。

实例代码

相关依赖包

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

<dependencies>

        <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

        <groupId>org.aspectj</groupId>

        <artifactId>aspectjrt</artifactId>

        <version>1.8.6</version>

    </dependency>

    <dependency>

        <groupId>org.aspectj</groupId>

        <artifactId>aspectjweaver</artifactId>

        <version>1.8.6</version>

    </dependency>

    <dependency>

        <groupId>org.springframework</groupId>

        <artifactId>spring-context</artifactId>

        <version>4.3.6.RELEASE</version>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-autoconfigure</artifactId>

        <version>1.3.8.RELEASE</version>

    </dependency>

    <dependency>

        <groupId>junit</groupId>

        <artifactId>junit</artifactId>

        <version>4.12</version>

        <scope>test</scope>

    </dependency>

    <dependency>

        <groupId>org.springframework</groupId>

        <artifactId>spring-test</artifactId>

        <version>4.2.8.RELEASE</version>

        <scope>test</scope>

    </dependency>

</dependencies>

定义和实现日志切面

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

package com.lingyejun.annotation;

 

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.*;

import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.context.annotation.Lazy;

import org.springframework.stereotype.Component;

 

import java.lang.reflect.Method;

 

/**

 * @Author: Lingye

 * @Date: 2018/11/11

 * @Describe:

 * 定义日志切面

 * @Lazy 注解:容器一般都会在启动的时候实例化所有单实例 bean,如果我们想要 Spring 在启动的时候延迟加载 bean,需要用到这个注解

 * value为true、false 默认为true,即延迟加载,@Lazy(false)表示对象会在初始化的时候创建

 *

 * @Modified By:

 */

@Aspect

@Component

@Lazy(false)

public class LoggerAspect {

 

    /**

     * 定义切入点:对要拦截的方法进行定义与限制,如包、类

     *

     * 1、execution(public * *(..)) 任意的公共方法

     * 2、execution(* set*(..)) 以set开头的所有的方法

     * 3、execution(* com.lingyejun.annotation.LoggerApply.*(..))com.lingyejun.annotation.LoggerApply这个类里的所有的方法

     * 4、execution(* com.lingyejun.annotation.*.*(..))com.lingyejun.annotation包下的所有的类的所有的方法

     * 5、execution(* com.lingyejun.annotation..*.*(..))com.lingyejun.annotation包及子包下所有的类的所有的方法

     * 6、execution(* com.lingyejun.annotation..*.*(String,?,Long)) com.lingyejun.annotation包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法

     * 7、execution(@annotation(com.lingyejun.annotation.Lingyejun))

     */

    @Pointcut("@annotation(com.lingyejun.annotation.Lingyejun)")

    private void cutMethod() {

 

    }

 

    /**

     * 前置通知:在目标方法执行前调用

     */

    @Before("cutMethod()")

    public void begin() {

        System.out.println("==@Before== lingyejun blog logger : begin");

    }

 

    /**

     * 后置通知:在目标方法执行后调用,若目标方法出现异常,则不执行

     */

    @AfterReturning("cutMethod()")

    public void afterReturning() {

        System.out.println("==@AfterReturning== lingyejun blog logger : after returning");

    }

 

    /**

     * 后置/最终通知:无论目标方法在执行过程中出现一场都会在它之后调用

     */

    @After("cutMethod()")

    public void after() {

        System.out.println("==@After== lingyejun blog logger : finally returning");

    }

 

    /**

     * 异常通知:目标方法抛出异常时执行

     */

    @AfterThrowing("cutMethod()")

    public void afterThrowing() {

        System.out.println("==@AfterThrowing== lingyejun blog logger : after throwing");

    }

 

    /**

     * 环绕通知:灵活自由的在目标方法中切入代码

     */

    @Around("cutMethod()")

    public void around(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取目标方法的名称

        String methodName = joinPoint.getSignature().getName();

        // 获取方法传入参数

        Object[] params = joinPoint.getArgs();

        Lingyejun lingyejun = getDeclaredAnnotation(joinPoint);

        System.out.println("==@Around== lingyejun blog logger --》 method name " + methodName + " args " + params[0]);

        // 执行源方法

        joinPoint.proceed();

        // 模拟进行验证

        if (params != null && params.length > 0 && params[0].equals("Blog Home")) {

            System.out.println("==@Around== lingyejun blog logger --》 " + lingyejun.module() + " auth success");

        else {

            System.out.println("==@Around== lingyejun blog logger --》 " + lingyejun.module() + " auth failed");

        }

    }

 

    /**

     * 获取方法中声明的注解

     *

     * @param joinPoint

     * @return

     * @throws NoSuchMethodException

     */

    public Lingyejun getDeclaredAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {

        // 获取方法名

        String methodName = joinPoint.getSignature().getName();

        // 反射获取目标类

        Class<?> targetClass = joinPoint.getTarget().getClass();

        // 拿到方法对应的参数类型

        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();

        // 根据类、方法、参数类型(重载)获取到方法的具体信息

        Method objMethod = targetClass.getMethod(methodName, parameterTypes);

        // 拿到方法定义的注解信息

        Lingyejun annotation = objMethod.getDeclaredAnnotation(Lingyejun.class);

        // 返回

        return annotation;

    }

}

自定义一个注解

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

package com.lingyejun.annotation;

 

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

 

/**

 * @Author: Lingye

 * @Date: 2018/11/11

 * @Describe:

 * @Modified By:

 */

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Lingyejun {

 

    /**

     * 何种场景下的通用日志打印

     *

     * @return

     */

    String module();

}  

调用切面类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

package com.lingyejun.annotation;

 

import org.springframework.stereotype.Component;

 

/**

 * @Author: Lingye

 * @Date: 2018/11/11

 * @Describe:

 * @Modified By:

 */

@Component

public class LoggerApply {

 

    @Lingyejun(module = "http://www.cnblogs.com/lingyejun/")

    public void lingLogger(String event) throws Exception {

        System.out.println("lingLogger(String event) : lingyejun will auth by blog address");

        throw new Exception();

    }

}  

测试代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

package com.lingyejun.annotation;

 

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.SpringApplicationConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

 

@RunWith(SpringJUnit4ClassRunner.class)

@SpringApplicationConfiguration(classes = Application.class)

public class AnnotationTest {

 

    @Autowired

    private LoggerApply loggerApply;

 

    @Test

    public void testAnnotationLogger() {

        try {

            loggerApply.lingLogger("Blog Home");

        catch (Exception e) {

            System.out.println("a exception be there");

        }

    }

}

效果展示

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值