AOP

AOP简介

AOP的底层是基于动态代理实现的,而动态代理实现有以下两种方式

  1. jdk动态代理:使用jdk中的Proxy,InvocationHanderl以及反射进行处理(jdk动态代理要求目标类必须实现接口)
  2. Oglib动态代理:第三方的工具库,创建代理对象,原理是继承,通过继承目标类,创建子类,但是目标类与方法都不是final的

动态代理可以在目标类源代码不改变的原来方法下进行更改,减少代码的重复,专注代码的逻辑,解耦合(使事务与非事务进行分离)

AOP:面向切面编程,可以使用以上的两种代理方法,把动态代理的实现步骤,方式都定义好了,让开发人员使用一种统一的方式

切面(Aspect):给目标类增加的功能就是切面,切面一般都是非业务方法,可以独立使用的。

AOP的使用注意事项:

  1. 什么功能下使用切面
  2. 合理安排切面的执行时间(哪一个方法之前,哪一个方法之后)
  3. 合理的安全切面的执行位置,对哪一类,哪一个方法增加功能

术语

Aspect:切面,表示增强的功能

JoinPoint:连接点,链接业务方法和切面的位置

PointOut:连接点方法的集合

目标对象:哪一个类增加功能

Advice:通知,切面功能执行的时间

AOP的实现

spring内部实现了AOP,在进行事务处理的时候使用了aop,但是在项目的开发的时候不使用spring的aop,springaop太过于笨重。我们经常使用aspectJ,一个专门做aop的框架,当然spring框架也集成了aspectj

切入点表达式(aspectj)

execution(权限名 返回值 方法名(参数))

execution(public * * (…)):指定切入点为任意公共方法

execution(* set*(…)):任意一个set方法

image-20220507090311046

任意类中的任意方法,比如service包中的userservice的index方法

image-20220507090606468:在service包或者子包里面任意类的任意方法。..出现在类名的时候,后面必须跟*,表示包或者子包下面的所有类

image-20220507090859800:表示所有包service子包所有类(接口中所有方法都为切入点)

加入依赖项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjtools -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>

注解

@Aspect是框架注解,表示当前的类是切面类,在此类中定义方法,要求是公共方法,方法名可以自定义,但是如果含有参数,只有一些参数类型卡哇伊使用

@Before前置通知注解

1
2
3
4
5
6
7
8
@Aspect
public class MyIncation {
//在目标方法之前进行执行,value表示切入点位置,在saygood(方法前面执行),如果拥有参数的话,需要在参数的位置使用Interger,String等等类型,不需要参数名称
@Before(value = "execution( void com.zss.service.impl.TestServiceImpl.*(..))")
public void myBefore(){
System.out.println("我是前置方法");
}
}

@AfterReturning后置通知

属性value表示切入点表达式,returning自定义变量,表示目标方法的返回值,自定义变量名必须和通知方法的形参名一样,当然也可以不接受返回值

1
2
3
4
5
6
7
8
9
//res是目标方法的返回值,可以根据返回值做切面的处理,如果没有返回值则为空,Object res相当于首先执行了sayhi等方法,之后拿到返回值进行后续处理
@AfterReturning(value = "execution( void com.zss.service.impl.TestServiceImpl.*(..))",returning = "res")
public void myAfter(Object res){
System.out.println("我是后置方法");

if (res==null){
System.out.println("返回值为空");
}
}

@Around环绕通知

环绕通知必须含有一个返回值,此时发环绕通知首先调用时,不是执行测试中的方法,而是执行了这里定义的myAround方法

1
2
3
4
5
6
7
8
9
10
11
12
13
/*可以在方法执行前,执行后都能增强功能,可以控制目标方法是否被执行,修改原来目标方法的执行结果,影响最后的调用
* ProceedingJoinPoint相当于jdk动态代理的method,执行目标方法,获得返回值*/
@Around(value = "execution( * com.zss.service.impl.TestServiceImpl.*(..))")
public Object myAround(ProceedingJoinPoint point) throws Throwable {
//实现环绕通知
Object result;
System.out.println("环绕通知前");
//目标方法调用
result=point.proceed();
System.out.println("提交事务");

return result;
}

那么我们如何控制该方法是否执行那???

1
JoinPoint表示切入点
1
2
3
4
//我们可以对这个进行判断,再去决定是否调用原方法
if (point==null){
//目标方法调用
result=point.proceed();}

当然我们也可以根据这个方法改变我们目标方法的返回值,环绕通知经常用于做事务,在执行前开启事务,执行完毕后关闭事务

@AfterThrowing

在程序发生异常时,执行的代码

1
2
3
4
@AfterThrowing(value = "execution( * com.zss.service.impl.TestServiceImpl.*(..))",throwing = "ex")
public void myThrowing(Exception ex){
System.out.println("发生了异常,发送短信");
}

image-20220507110115922

@After

最终通知,总是会执行,可以用来清除资源,不管中间是否会发生异常

创建配置文件

1
2
3
4
5
6
7
8
9
<!--声明目标-->
<bean id="someService" class="com.zss.service.impl.TestServiceImpl"/>

<!--声明切面-->
<bean id="myAspect" class="com.zss.handle.MyIncation"/>

<!--声明自动代理生成器,使用aspectj框架内部的功能,创建目标对象的代理对象,创建代理对象
是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象,所以目标对象就是被修改之后的代理对象,会把容器中所有的符合条件对象一次性生成代理对象-->
<aop:aspectj-autoproxy/>

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AppTest 
{
@Test
public void test01() {
String config="applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
TestService proxy=(TestService) applicationContext.getBean("testService");
System.out.println(proxy.getClass().getName());
//通过代理的对象执行方法,实现目标方法执行时,增强功能
proxy.sayGood();
}
}

image-20220507095210535

可以看出,此时的对象已经不再是testservice对象了,而是jdk动态代理生成的。

@Pointcut注解

如果切入点表达式含有多个重复的切入点。可以 使用此注解

1
2
3
4
@Pointcut(value = "execution( * com.zss.service.impl.TestServiceImpl.*(..))")
public void mypt(){
//无需代码
}

当别的使用时,可以直接进行引用

1
2
3
4
@AfterThrowing(value = "mypt()",throwing = "ex")
public void myThrowing(Exception ex){
System.out.println("发生了异常,发送短信");
}

注意

此时我们使用接口,此时aop使用的是jdk的动态代理方式,如果我们去除接口,此时使用的是cglib的动态代理。当然如果我们使用接口,也仍旧可以使用cglib的动态代理方式,但是需要在xml文件进行配置

1
<aop:aspectj-autoproxy proxy-target-class="true"/>