JAVA-Spring-AOP-1

AOP面向切面编程

1. AOP

• AOP (Aspect Orient Programming) 面向切面编程是从动态角度考虑程序的运行过程
• AOP底层,就是采用动态代理模式实现的,采用了两种代理,JDK的动态代理和CGLib的动态代理
• 实际上AOP就是动态代理的一种规范化,因为动态代理种类繁多,掌握较难,所以就规范了一套统一的方式,这就是AOP
• AOP把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,去用动态代理。

1.1 动态代理实现方式

JDK:
• jdk动态代理,要求jdk中有Proxy,Method,InvocationHandler创建代理对象
• jdk代理要求目标类必须实现接口

CGLib:
• 第三方的工具库,创建代理对象,原理是继承,通过继承目标类创建子类,子类就是代理对象
• CGLib要求目标类不能是final的 方法也不能是final的

1.2 动态代理的作用:

1. 可以在目标类源代码不改变的情况下去增加功能
2. 减少重复代码
3. 专注业务逻辑代码
4. 解耦合,让你的业务功能和日志,事务非业务功能分离

1.3 理解AOP面向切面编程

AOP Aspect:切面    给你目标类增加的功能就是切面,就比如要添加的日志 他就属于切面
切面的特点:一般都是非业务方法,可以独立使用
AOP Orient:面向    
AOP Programming:编程
    1. 需要在分析项目功能时,找出切面。(并不是所有功能都能当切面)
    2. 合理的安排切面的执行时间(在目标方法前面,还是在目标方法后面)
    3. 合理的安排切面执行的位置,在哪个类,在哪个方法增加增强功能

1.4 什么时候使用AOP技术

• 当你要给一个项目存在的类修改功能,但是原有的类的功能不完善,并且没有源代码,就可以使用aop增加功能
• 给项目中的多个类,增加一个相同的功能,使用aop
• 给业务方法增加是事务,日志输出

1.5 面向切面编程的术语

• Aspect:切面,表示增强的功能,就是一堆代码,完成某一个功能,非业务功能,
常见的切面功能:日志,事务,统计信息,参数检查,权限验证
• JoinPoint:连接点    连接业务方法和切面的位置,其实就是类中的业务方法
• Pointcut:切入点  指多个连接点方法的集合
• 目标对象:给哪个类的方法添加功能,这个类就是目标对象
• Advice:通知,  通知表示切面功能执行的时间,(在方法之前还是在方法之后)

1.6 一个切面有三个关键的要素:

1. 切面的功能代码,Aspect  切面要干什么
2. 切面的执行位置,Pointcut  也就是在那个方法里加功能
3. 切面执行的时间,Advice 也就是在方法前还是后

2. Aspect/切入点表达式

2.1 AOP

AOP是一个规范,是动态的一个规范化,一个标准

2.2 AOP的技术实现框架:

Spring:

spring在内部中实现了AOP规范,能做AOP的工作
spring主要在事务处理时使用AOP
我们项目开发中很少使用spring的AOP实现,因为springAOP比较笨重。

AspectJ:

一个开源的专门做AOP的框架,是业内最专业的AOP框架,又精又厉害
spring中已经集成了AspectJ的框架,所以通过spring就可以直接使用AspectJ的功能了

2.3 Aspect实现AOP有两种方式

1. 使用xml配置文件:配置全局事务
2. 使用注解,我们在项目中要做AOP功能,一般都是用注解,aspectJ有5个注解【表示切面的执行时间】

2.4 AspectJ框架的使用

切面的执行时间,这个执行时间在规范中,他叫做通知/增强 Advice
在AspectJ中使用注解来表示,当然也可以使用xml文件中的标签

• @Before
• @AfterReturning
• @Around
• @AfterThrowing
• @After

2.5 切入点表达式

表示切面执行的位置使用的是 切入点表达式

execution(modifiers-pattern? ret-type-pattren
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)

execution:关键字
小括号开始 小括号结束
第一个参数:方法的访问修饰符【modifiers-pattern】 后面问号表示可选,可以不填
然后一个空格
第二个参数:方法返回值的数据类型【ret-type-pattren】
第三个参数:方法所在的包名类名 ?【declaring-type-pattern】
第四个参数:方法名【name-pattern】
(param-pattern)方法的参数,只填写类型就行,形参名不需要
第五个:方法所抛出的异常

execution(方法的访问修饰符? 方法的返回值类型 方法所在的包名类名? 方法名(方法的参数) 方法所抛出的异常?)
? 表示可选的部分。
以上表达式最重要的四个部分
Execution(访问权限 方法返回值 方法声明(参数) 异常类型)

在parttern的地方表示都可以使用通配符

2.6通配符

符号 意义
* 0至多个任意字符
.. 用在方法参数中,表示任意多个参数用在包名后,表示当前包及其子包路径
+ 用在类名后面,表示当前类及其子类 用在接口后,表示当前接口及实现类

例如:

• execution(public * *(..))   指定切入点为:任意功能方法
• execution(* set*(..))  指定切入点为:任意一个以 set 开始的方法
• execution(* com.xyz.service.*.*(..)) 切入点:service包中的所有类 的所有方法参数任意(子包中不算)
• execution(* com.xyz.service..*.*(..)) 切入点:定义在service或者子包中任意类的任意方法
• ..出现在类名中时,后面必须跟”*“表示包,子包下的所有类
• execution(* *..service.*.*(..))  +   指定所有包下的service子包下所有类(接口)中所有方法为切入点
• execution(* *.service.*.*(..))    指定只有以及包下的service子包下所有类(接口)中所以后方法为切入点

3. AspectJ实现步骤(注解方式)

使用AspectJ框架实现AOP(给一些已经存在的类和方法,增加额外的功能【前提是不改变原来类的代码】)

1. 先创建一个maven功能

• 加入spring依赖
• 加入aspectJ依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

2. 创建目标类:必须有接口和他的实现类(底层目前是JDK动态代理)

给类中的方法增加功能

//目标类
@Component
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome(String s) {
        //给doSome方法增加功能,在方法执行之前 输出方法的执行时间
        System.out.println("SomeServiceImpl的doSome()方法执行了");
        System.out.println(s);
        return s;
    }
}

3. 创建切面类:就是一个普通类

a. 在类的上面添加注解 @Aspect
b. 在类中定义方法,方法就是切面要执行的功能代码
c. 在方法的上面加入aspectJ中的通知注解,例如@Before
d. 指定切入点表达式execution()  execution(public void       com.yixuexi.ba01.SomeServiceImpl.doSome(..))

/*
* 注解作用:AspectJ框架中的注解,表示当前类是切面类
* 切面类:是用来给业务方法增加功能的类,在这个类中有很多的功能代码
* 使用位置:类的上面
* */
@Component
@Aspect
public class MyAspect {
    /*在类里面定义方法,方法是实现切面功能的
    * 方法的定义要求
    * 1.公共方法
    * 2.方法没有返回值
    * 3.方法名称自定义
    * 4.方法可以有参数,也可以没有参数
    *   如果有参数,参数不是自定义的,有几个参数类型可以使用...
    *
    * */
    /*
    * @Before:前置通知注解
    * 属性:value  是切入点表达式,表示切面的功能执行的位置
    * 位置:在方法的上面添加注解@Before
    * 特点:
    *       1.在目标方法之前先执行
    *       2.不会改变目标方法的执行结果
    *       3.不会影响目标方法的执行
    * */
    @Before(value = "execution(public * *(..))")
    public void myBefore(JoinPoint joinPoint){
/*        System.out.println(joinPoint.getSignature().getDeclaringTypeName());
        System.out.println(joinPoint.getSignature());
        for (Object arg : joinPoint.getArgs()) {
            System.out.println(arg);
        }*/
        //切面要执行的功能代码
        System.out.println("前置通知,切面功能:在目标方法执行之前输出时间" + new Date());
    }
    /*保证returning的值和方法的形参名一致就行*/
    @AfterReturning(value = "execution(* *(..))", returning="res")
    public void doOther(Object res){
        res += "Hello world";
        System.out.println(res);
        System.out.println("后置通知执行");
    }
}

4. 创建spring的配置文件,在文件中声明对象,把对象交给容器统一管理(IOC管理)

• 声明对象可以使用xml或者注解的方式
• 声明目标对象(使用注解也可以)
    • <bean id="someService" class="com.yixuexi.ba01.SomeServiceImpl"></bean>
• 声明切面类对象(使用注解也可以)
    • <bean id="myAspect" class="com.yixuexi.ba01.MyAspect"></bean>
• 声明AspectJ框架中的自动代理生成器标签(自动代理生成器:完成代理对象自动创建功能)

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

5. 创建测试类,从spring容器中获取目标对象(实际就是代理对象)

a. 通过代理执行方法,实现aop的功能增强
b. 这个返回的就不是哪个类的对象了,而是他的接口的实现类

@org.junit.Test
public void test(){
    String path = "application-context.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(path);
    //从容器中获取目标对象,获取的目标对象不再是原来的类型 而是它的接口类型
    //实际上是 获取的就是代理对象
    String applicationName = ac.getApplicationName();
    System.out.println(applicationName);
    SomeService someServiceImpl = (SomeService) ac.getBean("someService");
    //通过代理对象执行方法,完成在调用方法时增强功能
    someServiceImpl.doSome("我是你爸爸");
}

4. 前置/后置/环绕通知注解

表示切面的执行时间使用的是通知注解

4.1 前置通知注解 @Before

• 在切面类的切面方法上使用@Before 表示在目标方法执行前执行(表示执行的时间,是在前还是在后)
• 属性:value是切入点表达式,表示切面的功能执行的位置
• 位置:在方法的上面添加注解@Before
• 特点:
      1.在目标方法之前先执行
      2.不会改变目标方法的执行结果
      3.不会影响目标方法的执行
• 前置通知方法的定义要求
1.公共方法
2.方法没有返回值
3.方法名称自定义
4.方法可以有参数,也可以没有参数
    如果有参数,参数不是自定义的,有几个参数类型可以使用...

4.2 后置通知 @AfterReturning

@AfterReturning

属性:

1. value 切入点表达式
2. Returning 自定义变量,表示目标方法的返回值
    自定义的变量名必须和通知方法的形参名一样

使用位置:在方法定义的上面

特点:

1. 在目标方法执行之后执行
2. 能够获取到目标方法的返回值,根据返回值做不同的处理功能
    Object res = 调用目标类的目标方法返回值
3. 可以修改返回值
    • 如果是引用传递 那么可以修改值,也就是说传递的是一个对象的话  返回值是可以在通知方法中修改的
    因为传递的是内存中的同一个对象,通知方法中修改的也是同一个对象
    • 如果是值传递,也就是说传递的是字符串或者是数字的话 那么返回值是不能在通知方法里修改的

后置通知方法

1. 公共方法
2. 方法没有返回值
3. 方法名称自定义
4. 方法有参数的 推荐使用Object

/*保证returning的值和方法的形参名一致就行*/
@AfterReturning(value = "execution(* *(..))", returning="res")
public void myAfterReturning(Object res){ 一致
    //res就是目标方法的返回值
    System.out.println("后置通知执行");
}  
后置通知的执行 res相当于:
Object res = doOther();   // 目标方法
myAfterReturning(res);    // 在把目标方法的返回值 传递给通知方法

@After和@AfterReturning的区别

@After通知是最终通知 也就是说就算抛出了异常 @After通知也是可以执行的,有点向finally
但是@AfterReturning不是 一但抛出异常@AfterReturning不执行

4.3 指定通知方法的形参

(被通知注解修饰的方法叫通知方法)

[所有的通知方法都能使用 必须是形参的第一位]
JoinPoint:代表的是业务方法 也就是@Before(“execution(public void doSome(..))”)里的doSome方法
要加入切面功能的业务方法

作用是:在通知方法中获取方法执行时的信息,例如方法名称,方法的实际参数
如果你的切面功能中需要用到方法的信息,就加入JoinPoint

使用要求:
JionPoint的值是框架自动赋予的,必须是第一个位置的参数

• 获取方法的形参   joinPoint.getArgs()    //返回一个String数组 ,遍历即可
• 获取方法的定义joinPoint.getSignature() 

5. 环绕/异常/最终通知注解

5.1 @Around环绕通知

经常用来做 事务 在目标方法之前开启事务,目标方法之后结束事务

环绕通知方法的定义和格式

1. public方法
2. 必须有返回值,推荐使用Object
3. 方法名称自定义
4. 方法有参数,固定参数       ProceedingJoinPoint

@Around("execution(public void doSome())")
public Object myAspect(ProceedingJoinPoint pjp) throws Throwable {
    return null;
}

@Around

1. 属性:value 切入点表达式
2. 位置:在方法的定义上面
3. 特点:
    a. 功能最强的通知
    b. 可以在目标方法的前/后执行。
        • 在pjp.proceed(); 方法上面的代码就是前,下面就是后
    c. 控制目标方法是否被调用执行
        • 通过 调不调用pjp.proceed();方法来控制目标方法是否被调用
        • 也可以 通过条件判断 如果xxx就执行 如果xxx不执行
        • ProceedingJoinPoint是接口 继承JoinPoint所以可以通过 他的getArgs()获取到参数然后进行判断
    d. 修改原来的目标方法的执行结果,影响最后的调用结果  
        •  最后的返回结果是环绕通知的return 决定的
4. 跟JDK的动态代理一样

参数ProceedingJoinPoint :

• 用来执行目标方法的 //Object obj  =  pjp.proceed(); 执行目标方法 接收返回值

返回值:

• 就是目标方法的执行结果,可以被修改
• [在测试类中写的 调用 doOther()  其实内部调用的是 myAspect()方法,这就是动态代理]
• 调用目标方法  == 调用myAround()

在pjp.proceed()上面的代码 是方法执行之前执行,下面是执行之后执行

5.2 @AfterThrowing 异常通知(了解即可)

在程序抛出异常是被触发调用
异常通知方法定义:

1. public  
2. 没有返回值 
3. 方法名字自定义
4. 方法可以有参数,如果有就是JoinPoint / Exception

public void myAfterThrowing(Exception e){
}

@AfterThrowing

属性:

• value 切入点表达式
• throwing 自定义的变量,表示目标方法抛出的异常对象
• throwing=“xx” xx必须和 Exception xx 一致

特点:

• 在目标方法抛出异常时执行的
• 可以做异常的监控程序,监控目标方法执行时是不是有异常,如果有异常可以发送邮件或者短信

执行原理就是:在底层其实是有一个try{}catch(){}的 如果遇到了异常就进入到异常 然后调用你的异常通知方法

5.3 @After最终通知

最终通知方法定义
1. public
2. 没有返回值
3. 名称自定义
4. 没参数,如果有JoinPoint

属性:value 切入点表达式

使用位置:方法的上面

特点:
1. 总是会执行(就算抛出了异常也是会执行的) 类似于finally
2. 在目标方法之后执行

作用:一般是用来做内存清除的

5.3 @Pointcut注解

• 当有很多的切入点表达式的时候,就比较难管理,这时就可以使用过@Pointcut注解
• 使用场景:用很多通知 都需要使用同一个切入点表达式。
• 属性: value 切入点表达式
• 位置:在自定义方法上面
• 特点:
    • 当使用了Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名。
    • 其他的通知中,value的属性就可以使用这个方法的名称来代替切入点表达式了

5.4 代理的使用方法

1. 如果目标类有接口,框架使用jdk动态代理
2. 如果目标类没有接口,默认使用的cglib动态代理
3. 有接口也可以强制使用cglib动态代理

<aop:aspectj-autoproxy proxy-target-class="true"/>
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
Contents
  1. 1. AOP面向切面编程
    1. 1.1. 1. AOP
      1. 1.1.1. 1.1 动态代理实现方式
      2. 1.1.2. 1.2 动态代理的作用:
      3. 1.1.3. 1.3 理解AOP面向切面编程
      4. 1.1.4. 1.4 什么时候使用AOP技术
      5. 1.1.5. 1.5 面向切面编程的术语
      6. 1.1.6. 1.6 一个切面有三个关键的要素:
    2. 1.2. 2. Aspect/切入点表达式
      1. 1.2.1. 2.1 AOP
      2. 1.2.2. 2.2 AOP的技术实现框架:
      3. 1.2.3. 2.3 Aspect实现AOP有两种方式
      4. 1.2.4. 2.4 AspectJ框架的使用
      5. 1.2.5. 2.5 切入点表达式
      6. 1.2.6. 2.6通配符
    3. 1.3. 3. AspectJ实现步骤(注解方式)
      1. 1.3.1. 1. 先创建一个maven功能
      2. 1.3.2. 2. 创建目标类:必须有接口和他的实现类(底层目前是JDK动态代理)
      3. 1.3.3. 3. 创建切面类:就是一个普通类
      4. 1.3.4. 4. 创建spring的配置文件,在文件中声明对象,把对象交给容器统一管理(IOC管理)
      5. 1.3.5. 5. 创建测试类,从spring容器中获取目标对象(实际就是代理对象)
    4. 1.4. 4. 前置/后置/环绕通知注解
      1. 1.4.1. 4.1 前置通知注解 @Before
      2. 1.4.2. 4.2 后置通知 @AfterReturning
      3. 1.4.3. 4.3 指定通知方法的形参
    5. 1.5. 5. 环绕/异常/最终通知注解
      1. 1.5.1. 5.1 @Around环绕通知
      2. 1.5.2. 5.2 @AfterThrowing 异常通知(了解即可)
      3. 1.5.3. 5.3 @After最终通知
      4. 1.5.4. 5.3 @Pointcut注解
      5. 1.5.5. 5.4 代理的使用方法
|