0%

java之spring AOP

AOP说明

  • 面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。

  • AOP是Spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC做补充。

  • 通过Proxy的方式,将重复的公共的代码抽离出去,动态的织入XXService(业务逻辑)中,而不改变原有的Service中的代码结构

AOP的通知类型

  • 前置通知(Before advice): 在连接点之前运行但无法阻止执行流程进入连接点的通知(除非它引发异常)。
  • 后置返回通知(After returning advice):在连接点正常完成后执行的通知(例如,当方法没有抛出任何异常并正常返回时)。
  • 后置异常通知(After throwing advice): 在方法抛出异常退出时执行的通知。
  • 后置通知(总会执行)(After (finally) advice): 当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
  • 环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型,。环绕通知可以在方法调用前后完
  • 自定义的行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。

依赖包

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
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- spring架包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>

实例

  • 本次实例为日志的使用方式

  • 编写dao Calculator接口

1
2
3
4
5
6
7
8
9
package xyz.shi.dao;

public interface Calculator {
public int add(int i,int j);
public int sub(int i,int j);
public int mul(int i,int j);
public int div(int i,int j);
}

  • LogUtil代码,加上Component注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package xyz.shi.util;

import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.stereotype.Component;
@Component
public class LogUtil {
public static void start(Method method, Object ... objects){
System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(objects));
}
public static void stop(Method method,Object ... objects){
System.out.println(method.getName()+"方法执行完成,参数是:"+ Arrays.asList(objects));
}

public static void logException(Method method,Exception e){
System.out.println(method.getName()+"方法出现异常:"+ e.getMessage());
}
public static void end(Method method){
System.out.println(method.getName()+"方法执行结束了......");
}
}
  • MyCalculatorService代码添加@Service注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package xyz.shi.service;

import org.springframework.stereotype.Service;

@Service(value = "myCalculatorService")
public class MyCalculatorService {
public int add(int i, int j) {
return i + j;
}
public int sub(int i, int j) {
return i - j;
}
public int mul(int i, int j) {
return i * j;
}
public int div(int i, int j) {
return i / j;
}
}
  • 添加自动扫描的配置,resources-application.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--扫描整个目标文件夹 -->
    <context:component-scan base-package="xyz.shi" />
    </beans>
  • 在LogUtil.java中添加@Aspect注解

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
package xyz.shi.util;


import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
@Component
@Aspect
public class LogUtil {
@Before("execution(public int xyz.shi.service.MyCalculatorService.*(int,int))")
public static void start(){
System.out.println("方法开始执行,参数是:");
}
@AfterReturning("execution(public int xyz.shi.service.MyCalculatorService.*(int,int))")
public static void stop(){
System.out.println("方法执行完成,结果是:");

}
@AfterThrowing("execution(public int xyz.shi.service.MyCalculatorService.*(int,int))")
public static void logException(){
System.out.println("方法出现异常:");
}
@After("execution(public int xyz.shi.service.MyCalculatorService.*(int,int))")
public static void end(){
System.out.println("方法执行结束了......");
}
}




  • 开启基于注解的aop的功能,resources-application.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<?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: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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描整个目标文件夹 -->
<context:component-scan base-package="xyz.shi" />
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

  • 执行测试
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import xyz.shi.dao.Calculator;

public class MyTest {
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Calculator calculator=context.getBean( "myCalculatorService",Calculator.class);
calculator.add(1, 2);
}
}
  • 结果
1
2
3
4
方法开始执行,参数是:
方法执行完成,结果是:
方法执行结束了......

cglib来创建代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package xyz.shi.service;

import org.springframework.stereotype.Service;

@Service
public class MyCalculatorService {
public int add(int i, int j) {
return i + j;
}

public int sub(int i, int j) {
return i - j;
}

public int mul(int i, int j) {
return i * j;
}

public int div(int i, int j) {
return i / j;
}
}

只需要把我们的MyCalculatorService.java中实现的Calculator.java的接口去掉就行了

  • 测试代码,直接调用service层代码
1
2
3
4
5
6
7
8
9
10
11
12
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import xyz.shi.service.MyCalculatorService;

public class MyTest {
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
MyCalculatorService myCalculator = context.getBean("myCalculatorService", MyCalculatorService.class);
myCalculator.add(1,2);
}
  • 当然也可以不写xml配置文件,直接写在class中,比如
1
2
3
4
5
6
7
8
9
10
11
12
package xyz.shi.config;

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

@Configuration
@ComponentScan("xyz.shi")
//注意开启AOP的支持,并且代理设置为cglib动态代理,因为UserService没有接口
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
}
  • 测试代码需要改变下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import xyz.shi.config.SpringConfig;
import xyz.shi.service.MyCalculatorService;

public class MyTest {
@Test
public void test01() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
MyCalculatorService myCalculator = context.getBean("myCalculatorService", MyCalculatorService.class);
myCalculator.add(1,2);
}
}

xml的AOP配置

  • 这里没有实践
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
<?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: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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<context:component-scan base-package="xyz.shi"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

<bean id="myCalculatorService" class="xyz.shi.service.MyCalculatorService"></bean>
<bean id="logUtil" class="xyz.shi.util.LogUtil"></bean>

<aop:config>

<aop:aspect ref="logUtil">
<aop:pointcut id="logPoint" expression="execution(public int xyz.shi.service.MyCalculatorService.*(int,int))"/>
<aop:before method="start" pointcut-ref="logPoint"></aop:before>
<aop:after method="end" pointcut-ref="logPoint"></aop:after>
<aop:after-returning method="stop" pointcut-ref="logPoint" returning="result"></aop:after-returning>
<aop:after-throwing method="logException" pointcut-ref="logPoint" throwing="e"></aop:after-throwing>
<aop:around method="myAround" pointcut-ref="logPoint"></aop:around>
</aop:aspect>
</aop:config>
</beans>

切点表达式

  • 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)

execution

  • execution(public User com.mszlu.service.UserService.findById(int))

  • execution在实际工作中,很少被使用,因为匹配的打击面非常大或者非常小,不能灵活应用

  • execution(public * com.mszlu.service.*.*(..)) 基本能实现无差别全覆盖,即某个包下面的所有Bean的所有方法都会被拦截。

    execution(public * update*(..))从方法的前缀来区分,这种误伤的概率非常大,你不可能要求所有的程序员都按照这种书写习惯来。

annotation

  • 我们使用AOP的时候,常常使用annotation的形式

  • 场景:比如我们实现一个性能监控的需求,使用AOP实现

  1. 定义注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.mszlu.aop;

    import java.lang.annotation.*;

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MsMetric {
    String value() default "";
    }
  2. 在需要监控的方法上,加上注解

    1
    2
    3
    4
    @MsMetric
    public void registerUser(String mail,String password,String nickname){
    //...
    }
  3. 实现性能监控AOP,切点使用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
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    package com.mszlu.aop;

    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;

    @Aspect
    @Component
    @Slf4j
    public class MetricAspect {

    // 定义切点 注解所在的方法 即是切点
    @Pointcut("@annotation(MsMetric)")
    public void pt() {}

    //定义通知
    @Around("pt()")
    public Object doLogging(ProceedingJoinPoint pjp) throws Throwable{
    try {
    log.info("----------------metric start--------------------");
    //计算方法执行的时间
    long startTime = System.currentTimeMillis();
    //方法调用
    Object ret = pjp.proceed();
    long endTime = System.currentTimeMillis();
    log.info("方法执行时间:{}ms", endTime-startTime);
    log.info("----------------metric end--------------------");
    return ret;
    }catch (Exception e){
    log.error("异常信息",e);
    throw e;
    }
    }
    }