loading...
AOP简介以及开发步骤
Published in:2022-01-31 | category: Spring
Words: 2.8k | Reading time: 11min | reading:

AOP

什么是AOP?

AOP是面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP的作用以及优势

  • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
  • 优势:减少重复代码,提高开发效率,并且便于维护

AOP底层实现

实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

AOP的动态代理技术

常用的动态代理技术

  • JDK代理:基于接口的动态代理技术
  • cglib代理:基于父类的动态代理技术

JDK动态代理

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
//目标对象
Target target = new Target();


//增强对象
Advice advice = new Advice();


//返回值是动态生成的代理对象
TargetInterface targetInterface = (TargetInterface)Proxy.newProxyInstance(target.getClass().getClassLoader(), //目标对象类加载器
target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
new InvocationHandler() {

//调用代理对象的任何方法,实质执行的都是invoke方法
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//前置方法
advice.before();
//执行目标方法
Object invoke = method.invoke(target, args);

//后置增强
advice.after();
return invoke;

}
}
);

//调用代理对象的方法
target.save();

cglib动态代理

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
//目标对象
Target target = new Target();


//增强对象
Advice advice = new Advice();


//返回值 就是动态生成的代理对象 基于cglib
//1.创建增强器
Enhancer enhancer = new Enhancer();
//2.设置父类(目标)
enhancer.setSuperclass(Target.class);
//3.设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

//执行前置
advice.before();;
//执行目标
Object invoke = method.invoke(target, args);
//执行后置
advice.after();
return invoke;

}
});

//4.创建代理对象
Target proxy = (Target) enhancer.create();

//执行方法
proxy.save();

AOP相关概念

AOP的实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。

AOP中相关的常用术语:

  • Target(目标对象):代理的目标对象
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
  • Joinpoint(连接点):可以被增强的方法,就叫做连接点
  • Pointcut(切入点):真正要被增强的方法,就叫做切入点
  • Advice(通知/增强):所谓通知就是指将拦截到的目标方法之后所要做的事情就是通知
  • Aspect(切面):是切入点和通知的结合
  • Weaving(织入):就是将切入点和通知结合到一起的过程就叫做织入。

AOP开发明确的事项

  1. 需要编写的内容

    • 编写核心业务代码(目标类的目标方法)
    • 编写切面类,切面类中有通知(增强功能方法)
    • 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
  2. AOP技术实现的内容

    Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

  3. AOP底层使用哪种代理方式

    在spring中,框架会根据目标类是否实现了接口来决定采用哪种代理方式。

知识要点

  • aop:面向切面编程

  • aop底层实现:基于JDK的动态代理 和 基于Cglib的动态代理

  • aop的重点概念:

    Pointcut(切入点):被增强的方法

    Advice(通知/增强):封装增强业务逻辑的方法

    Aspect(切面):切点+通知

    Weaving(织入):将切点与通知结合的过程

  • 开发明确事项

    谁是切点(切点表达式的配置)

    谁是通知(切面类中的增强方法)

    将切点和通知进行织入配置

基于XML的AOP开发

快速入门

① 导入AOP相关坐标

②创建目标接口和目标类(内部有切点)

③创建切面类(内部有增强方法)

④将目标类和切面类的对象创建权交给Spring

⑤在applicationContext.xml中配置织入关系

⑥测试代码

导入AOP相关坐标

1
2
3
4
5
6
7
8
9
10
11
12
<!--导入spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>

创建目标接口和目标类(内部切点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

/**
*目标接口
*/
public interface TargetInterface {

void method();
}


/**
*目标类
*/
public class Target implements TargetInterface {

@Override
public void method() {
System.out.println("Target Running.......");
}
}

创建切面类(内部有增强方法)

1
2
3
4
5
6
7
8
9
public class MyAspect {
/**
* 前置增强方法
*/

public void before(){
System.out.println("前置代码增强......");
}
}

将目标类和切面类的创建权交给Spring

1
2
3
4
<!--配置目标类-->
<bean id="target" class="com.yr.service.impl.Target"></bean>
<!--配置切面类-->
<bean id="myAspect" class="com.yr.utils.MyAspect"></bean>

在applicationContext.xml中配置织入关系

  • 导入aop命名空间
1
2
3
4
5
6
7
8
9
10
11
<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/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
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
  • 配置切点表达式和前置增强的织入关系
1
2
3
4
5
6
7
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配r置Target的method方法执行时要进行myAspect的before方法前置增强-->
<aop:before method="before" pointcut="execution(public void com.yr.service.impl.Target.method())"></aop:before>
</aop:aspect>
</aop:config>

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface targetInterface;

@Test
public void test1(){
targetInterface.method();
}
}

测试结果

rtcFb9.png

XML配置AOP详解

切点表达式的写法

表达式语法:

1
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
  • 访问修饰符可以省略
  • 返回值类型、包名、类名、方法名可以使用星号* 代表任意
  • 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
  • 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表

例如:

1
2
3
4
5
execution(public void com.itheima.aop.Target.method())	
execution(void com.itheima.aop.Target.*(..))
execution(* com.itheima.aop.*.*(..))
execution(* com.itheima.aop..*.*(..))
execution(* *..*.*(..))

通知的类型

通知的配置语法:

1
<aop:通知类型 method="切面类中方法名" pointcut="切点表达式"></aop:通知类型>
名称 标签 说明
前置通知 < aop:before > 用于配置前置通知,指定增强方法在切入点方法之前执行
后置通知 < aop:after-returning > 用于配置后置通知,指定增强的方法在切入点方法之后执行
环绕通知 < aop:around > 用于配置环绕通知,指定增强的方法在切入点之前和之后都执行
异常抛出通知 < aop:throwing > 用于配置异常抛出通知,指定增强的方法在出现异常时执行
最终通知 < aop:after > 用于配置最终通知,无论增强方式执行是否有异常都会执行

配置代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--前置增强-->
<aop:before method="before" pointcut="execution(public void com.aop.service.impl.Target.method())"></aop:before>

<!--后置增强-->
<aop:after-returning method="afterReturning" pointcut="execution(* com.aop.service.impl.*.*(..))"></aop:after-returning>

<!--环绕增强-->
<aop:around method="around"pointcut="execution(*com.aop.service.impl.*.*(..))"></aop:around>

<!-- 抛出异常增强-->
<aop:after-throwing method="afterReturning" pointcut="execution(* com.aop.service.impl.*.*(..))"></aop:after-throwing>


<!--最终增强-->
<aop:after method="after" pointcut="execution(* com.aop.service.impl.*.*(..))"></aop:after>

切点表达式的抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取后的切点表达式。

1
2
3
4
5
6
7
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<aop:ponitcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"/>
<aop:before method="before" ponitcut-ref="myPointcut"></aop:before>
</aop:aspect>
</aop:config>

知识要点

  • aop织入的配置

    1
    2
    3
    4
    5
    <aop:config>
    <aop:aspect ref="切面类">
    <aop:before method="通知方法名称" pointcut="切点表达式"></aop:before>
    </aop:aspect>
    </aop:config>
  • 通知的类型:前置通知,后置通知,环绕通知,异常抛出通知,最终通知

  • 切点表达式的写法

    1
    execution([修饰符] 返回值类型 包名.类名.方法名(参数m))

基于注解的AOP开发

快速入门

基于注解的aop开发步骤:

  1. 创建目标接口和目标类(内部有切点)
  2. 创建切面类(内部有增强方法)
  3. 将目标类和切面类的对象创建权交给spring
  4. 在切面类中使用注解配置织入关系
  5. 在配置文件中开启组件扫描和AOP的自动代理
  6. 测试

注解配置AOP详解

注解通知的类型

通知的配置语法:@通知注解(“切点表达式”)

名称 注解 说明
前置通知 @Before 用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知 @AfterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行
环绕通知 @Around 用于配置环绕通知。指定增强的方法在切入点之前和之后都执行
异常抛出通知 @AfterThrowing 用于配置异常抛出通知。指定增强的方法在出现异常时执行
最终通知 @After 用于配置最终通知。无论增强方式执行是否有异常都会执行

切点表达式的抽取

同xml配置aop一样,我们可以将切点表达式抽取。抽取的方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在增强注解中进行引用。具体如下:

1
2
3
4
5
6
7
8
9
10
11
@Component("myAspect")
@Aspect
public class MyAspect{
@Before("MyAspect.myPoint()")
public void before(){
System.out.println("前置代码增强.....")
}

@Pointcut("execution(* com.ithemima.aop.*.*(..))")
public void myPoint(){}
}

知识要点

  • 注解aop开发步骤
    1. 使用@Aspect标注切面类
    2. 使用@通知注解标注通知方法
    3. 在配置文件中配置aop自动代理< aop:aspectj-autoproxy />
Prev:
Quartiz 定时任务的数据库表介绍
Next:
RequestBody 获取请求参数解决java.io.IOException
catalog
catalog