AOP面向切口编程:
AOP简介 :
AOP (Aspect Orient Programming)
- Aspect : 切面 : 给你的目标类增加的功能 , 就是切面
- 切面的特点 : 一般都是非业务逻辑 , 都是可以独立使用的
- Orient : 面向 , 对着
- Programming : 编程
意为 : 面向切面编程 , 可通过运行期动态代理实现程序功能的统一维护的一种技术 , AOP是Spring框架中一个重要内容 , 利用AOP可以对业务逻辑的各个部分进行隔离 , 从而使得业务逻辑各部分之间耦合度降低 , 提高程序的可重用性 , 同时提高了开发效率
AOP底层 , 就是采用的动态代理模式实现的 , 采用两种代理 ,JDK动态代理 和 CGLIB动态代理
面向切面编程 , 就是将交叉业务逻辑封装成切面 , 利用AOP容器的功能将切面织入到主业务逻辑中 , 所谓交叉业务逻辑是指 , 通用的 , 与主业务逻辑无关的代码 , 如安全检查 , 事务 , 日志 , 缓存等
如若不使用AOP , 则会出现代码的纠缠 , 级交叉业务逻辑与主业务逻辑混合在一起 , 这样 , 会使主业务逻辑变得混杂不清
AOP就是动态代理的规范化 , 把动态代理的实现步骤 , 方式都定义好了 , 让开发人员用一种统一的方式 , 使用动态代理
怎么理解面向切面编程 :
-
(1) 需要在分析项目功能时 , 找出切面
-
(2) 合理的安排切面的执行时间 , (在目标方法前 , 还是目标方法后 )
-
(3) 合理的安全切面执行的位置 , 在哪个类 , 哪个方法增加
术语 :
-
(1) Aspect : 切面 表示增强的功能 , 非业务逻辑功能
- 常见的切面功能有 : 日志 , 事务 , 统计信息 , 参数检查 , 权限验证
-
(2) JoinPoint : 连接点 , 连接业务方法和切面的位置 , 就是某个类中的业务方法 (一个方法)
-
(3) Pointcut : 切入点 , 指多个连接点方法的集合 (多个方法)
-
(4) 目标对象 : 给哪个类的方法增加功能 , 这个类就是目标对象
-
(5) Advice : 通知 , 通知表示切面功能执行的时间
一个切面有三个关键的要素 :
-
(1) 切面的功能代码 , 切面干什么
-
(2) 切面的执行位置 , 使用Pointcut 表示切面执行的位置
-
(3) 切面的执行时间 , 使用Advice , 在目标方法之前 , 还是目标方法之后
AOP的实现 :
AOP是一个规范 , 是动态的一个规范化 , 一个标准
Spring框架监控切入点方法的执行 , 一旦监控到切入点方法被运行 , 使用代理机制 , 动态代理机制 , 动态创建目标对象的代理对象 , 根据通知类别 , 在代理对象的对应位置 , 将通知对应的功能织入 , 完成完整的代码的逻辑运行
AOP的技术实现框架 :
1.Spring : Spring 在内部实现了aop规范 , 能够做aop的工作
Spring主要是在事务处理时使用aop
我们项目开发中很少使用Spring的aop实现 , 因为spring的aop比较笨重
2.aspectJ : 一个开源的专门做aop的框架 , Spring框架中已经集成了aspectJ框架 , 通过Spring 就能使用aspectJ的功能
- 1.使用xml的配置文件 : 配置全局事务
- 2.使用注解 , 我们在项目中要做AOP功能 , 一般都是用注解
AOP开发明确的事项:
- 编写核心业务代码(目标类的目标方法)
- 编写切面类 , 切面类中有通知(增强功能方法)
- 在配置文件中 , 配置织入关系 , 即将那些通知与那些连接点进行结合
AOP:基于XML的AOP开发
-
(1)导入AOP相关坐标
-
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.4</version> </dependency>
-
-
(2)创建目标接口和目标类 (内部有切点)
-
(3)创建切面类 , (内部有增强方法)
-
(4)将目标类和切面类的对象创建权交给Spring
-
(5)在applicationContext.xml中配置织入关系
-
<!--目标对象--> <bean id="target" class="com.sichen.aop.Target"/> <!--切面对象--> <bean id="myAspect" class="com.sichen.aop.MyAspect"/> <!--配置织入 , 告诉spring框架 , 那些方法(切点) 需要进行那些增强(前置, 后置...)--> <aop:config> <!--声明切面--> <aop:aspect ref=""> <!--切面 , 切点+通知--> <aop:before method="切面对象中的方法名称" pointcut="切入点表达式"></aop:before> </aop:aspect> </aop:config>
AspectJ框架:
学习aspectJ框架的使用:
(1) 切面的执行时间 : 这个执行时间在规范中叫做通知 Advice (通知 , 增强)
在AspectJ框架中使用注解来表示的 , 也可以使用XML文件中的标签
- @Before
- @AfterReturning
- @Around
- @AfterThrowing
- @After
(2) 表示切面执行的位置 : 使用的是切入点表达式 ,
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(parm-pattern)
throws-pattern?)
解释 :
- modifiers-pattern : 访问权限类型
- ret-type-patern : 返回值类型
- declaring-type-pattern : 包名类名
- name-pattern(param-pattern) 方法名(参数类型和参数个数)
- throws-pattern : 抛出异常类型
? 表示可选部分
以上表达式共四个部分
execution(访问权限 方法返回值 方法申明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名 , 所以 execution 表达式中明显就是方法的签名 , 注意 , 表达式中黑色文字表示可以省略部分 , 各部分间用空格分开 , 在其中可以使用以下符号
举例 :
- execution ( public(权限修饰符) ***** (返回值) *(…) (方法声明) )
- 指定切入点为 : 任意公共方法
- *execution( *** (返回值) set(…) (方法声明) )
- 指定切入点为 : 任意一个以set开头的方法
- execution ( *** (返回值) com.xyz.service. . * (…) (方法声明))*
- 指定切入点为 : service包中的任意类 , 类中的任意方法 ,
- 第一个*表示包下的所有类
- 第二个*表示类下的所有方法
- **execution( *** (返回值) com.xyz.service… . (…) (方法声明) )
- 指定切入点为 : 定义在service包或者子包里的任意类的任意方法 ,
- " … " 出现在类名中时 , 后面必须跟 " * " , 表示包 , 子包下所有的类1
- execution( * (返回值) * …service.* . *(…) (方法声明))
- 指定切入点为 : 所有包下的service子包下所有类(接口)中所有方法为切入点
aspectJ的使用 :
(1) 新建maven项目
(2) 加入依赖 :
- spring依赖
- aspectJ依赖
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.14</version>
</dependency>
<!--aspectJ依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.14</version>
</dependency>
(3) 创建目标类 : 接口和实现类
要做的就是给类中的方法增加功能
(4) 创建切面类 : 普通类
-
在类的上面要加入 @Aspect
-
/** * @Aspect : 是aspectj框架中的注解 * 作用 : 表示当前类是切面类 */
-
在类中定义方法 , 方法就是切面要执行的功能代码
- 在方法的上面要加入aspectJ中的通知注解 , 例如@before
- 还需要指定切入点表达式 execution()
@Aspect
public class MyAspect {
@Before(value="execution(public void com.sichen.DI01.impl.SomeServiceImpl.dosome(String,Integer))")
public void myBefore(){
System.out.println("前置通知 , 方法执行的时间..."+ new Date());
}
}
(5) 创建spring的配置文件 , 声明对象 , 把对象交给容器同一管理
- 声明对象可以使用注解或者是xml配置文件
- 声明目标对象
- 声明切面类对象
- 声明aspectJ框架中的自动代理生成器标签
- 自动代理生成器 : 用来完成代理对象的自动创建功能的
<!--把对象交给spring容器 , 有spring容器同一管理 , 管理对象-->
<bean id="someService" class="com.sichen.DI01.impl.SomeServiceImpl"/>
<!--声明切面类对象-->
<bean id="myAspect" class="com.sichen.DI01.MyAspect"/>
<!--
自动代理生成器 : 使用aspectJ框架内部的功能 , 创建目标对象的代理对象
创建代理对象是在内存中实现的 , 修改目标对象的内存中的结构 , 创建为代理对象
aspectj-autoproxy
他会把spring容器中所有的目标对象 , 一次性都生成代理对象
-->
<aop:aspectj-autoproxy/>
注解中的参数 : (JoinPoint)
/**
* 指定通知方法中的参数 : JoinPoint
* JoinPoint : 业务方法 , 要加入切面功能的业务方法 ,
* 作用是 : 可以在通知方法中获取方法执行时的信息 , 例如方法名称 , 方法的实参
* 如果你的切面功能中需要用到这个方法的信息 , 使用JoinPoint即可
* 要求 : 这个JoinPoint参数的值是由框架赋予的 , 必须是第一个位置的参数
*/
@Before(value="execution(public void com.sichen.DI01.impl.SomeServiceImpl.dosome(String,Integer))")
public void myBefore(JoinPoint jp){
//获取方法的完整定义
System.out.println("方法的签名 (定义)=" + jp.getSignature());
System.out.println("方法的名字="+ jp.getSignature().getName());
//获取方法的实参
Object[] args = jp.getArgs();
for (Object arg : args) {
System.out.println("方法的参数是 : " +arg);
}
System.out.println("前置通知 , 方法执行的时间..."+ new Date());
}
aspectJ中的五种注解的详解:
@Before : 前置通知
-
@Before 前置通知注解 方法的定义要求 : 1.公共方法 public 2.方法/*没有返回值*/ 3.方法名称自定义 4.方法可以有参数 , 也可以没有参数 如果有参数 , 参数(不是自定义的) , 有几个参数类型可以选择
-
属性 : value
- 是切入点表达式 ,表示切面功能执行的位置
-
位置 :
- 在方法的上面
-
特点 :
-
1.在目标方法之前先执行的
-
2.不会改变目标方法的执行结果
-
3.不会影响目标方法的执行
-
-
指定通知方法中的参数 : JoinPoint
- 作用是 :
- 可以在通知方法中获取方法执行时的信息 , 例如方法名称 , 方法的实参
- 如果你的切面功能中需要用到这个方法的信息 , 使用JoinPoint即可
- 要求 :
- 这个JoinPoint参数的值是由框架赋予的 , 必须是第一个位置的参数
- 作用是 :
@Before(value="execution(public void com.sichen.DI01.impl.SomeServiceImpl.dosome(String,Integer))")
public void myBefore(JoinPoint jp){
//获取方法的完整定义
System.out.println("方法的签名 (定义)=" + jp.getSignature());
System.out.println("方法的名字="+ jp.getSignature().getName());
//获取方法的实参
Object[] args = jp.getArgs();
for (Object arg : args) {
System.out.println("方法的参数是 : " +arg);
}
System.out.println("前置通知 , 方法执行的时间..."+ new Date());
}
@AfterReturning : 后置通知
-
@AfterReturning : 后置通知 方法的定义要求 : 1.公共方法 public 2.方法/*没有返回值*/ 3.方法名称自定义 4.方法可以有参数 , 推荐使用Object ,参数是自定义的
-
参数的作用:
- 1.在目标方法执行之后执行的
- 2.它能够获取目标方法的返回值
- Object res = doSome();
- 3.可以修改返回值
-
属性 :
- 1.value , 切入点表达式
- 2.returning : 自定义的变量 , 表示目标方法的返回值的
-
自定义的变量名必须和通知方法的形参名一样
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
returning = "res")
public void myAfterReturning(Object res) {
//Object res : 是目标方法执行之后的返回值 , 根据返回值做你的切面的功能处理
System.out.println("后置通知 : 目标方法的返回值是 = " + res);
}
@Around :环绕通知
环绕通知 : 经常做事务 , 在目标方法之前开启事务 , 执行目标方法 , 在目标方法之后提交事务
环绕通知方法的定义格式
1.公共方法 public
2./*必须有一个返回值*/ , 推荐使用Object
3.方法名称自定义
4.方法/*有参数*/ , 固定的参数 ProceedingJoinPoint
@Around :环绕通知
属性 : value : 切入点表达式
特点 :
1.它是/*功能最强*/的通知
2.在目标方法的/*前后段都能增强功能*/
3.它能/*控制目标方法是否被调用执行*/
4./*修改*/原来的/*目标方法*/的/*执行结果*/ , /*影响最后的调用结果*/
环绕通知 , 等同于JDK中的invoke()方法
参数 :
ProceedingJoinPoint :等同于动态代理中的Method
作用 : 执行目标方法的
它继承的是JoinPoint , 所以可以直接使用JoinPoint中的方法
返回值 : 是目标方法的执行结果 , 可以被修改
它相当于是把目标方法替换为了切面方法 , 这样就可以对该方法进行各种修改 ,
@Around(value = "execution( * *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pj) throws Throwable {
String name = "";
Object[] args = pj.getArgs();
if (args != null && args.length > 1) {
Object arg = args[0];
name = (String) arg;
}
//实现环绕通知 :
System.out.println("环绕通知 : 在目标方法之前输出时间" + new Date());
Object result = null;
//1.目标方法的调用
if ("思尘".equals(name)) {
result = pj.proceed();
//等同于Method中的invoke()方法 ;
//Object result = doFirst();
}
System.out.println("环绕通知 : 在目标方法之后 , 提交事务");
//2.在目标方法的前后加入功能
//修改目标方法的执行结果 , 影响方法最后的调用结果
if (result != null) {
result = "hello AspectJ AOP";
}
//返回目标方法的返回值
return result;
}
@AfterThrowing : 异常通知
在目标方法抛出异常后执行 , 该注解的 throwing 属性用于指定所发生的异常类对象 , 当然 , 被注解为异常通知的方法 , 可以包含一个参数 Throwable , 参数名称为 throwing 指定的名称 , 表示发生的异常对象
异常通知方法的定义格式
1.公共方法 public
2./*没有返回值*/
3.方法名称自定义
4.方法/*有一个参数 , Exception */
如果还有 , 就是JoinPoint
@AfterThrowing : 异常通知
属性 : 1.value 切入点表达式
2.throwing 自定义的变量 , 表示目标方法抛出的异常对象
/* 变量名必须和方法的参数名一样 */
特点 :
1./*在目标方法抛出异常时执行的*/
2.可以做异常的监控程序 , 监控目标方法执行时是不是有异常
如果有异常 , 可以发送邮件 , 短信进行通知
/*
执行过程 :
try {
目标方法的执行;
}catch(Exception e){
myAfterThrowing(e)
}
*/
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))" , throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("方法在异常时执行"+ex.getMessage());
//发送邮件或短信通知开发人员
}
@After : 最终通知
无论目标方法是否抛出异常 , 该增强就会被执行 ,
最终通知的定义 :
1. 公共方法 public
2. /* 没有返回值 */
3. 方法名称自定义
4. 方法/* 没有固定参数 */, 可以有 JoinPoint
@After : 最终通知
属性 : value : 切入点表达式
位置 : 在方法上边
特点 :
1.总是会执行
2.在最后执行的
一般是做资源清除工作的
@Pointcut : 定义和管理切入点的 (代替重复的切入点表达式)
如果你的项目中有多个切入点表达式是重复的,可以复用的,可以使用@Pointcut
属性 : value 切入点表达式
位置 : 自定义的方法上面
特点 :
- 当使用@Pointcut定义在一个方法上边 , 此时这个方法的名称就是切入点表达式的别名
- 其他通知中 , value属性就可以使用这个方法名称(记得方法名后边要加括号) , 代替切入点表达式了
有接口和没接口使用的代理方式 :
有接口使用的是JDK的动态代理
没有接口使用的是cglib动态代理 (内部实现是 : 使用继承的方法生成目标类的子类 , 继承之后 重写方法)
在aspectJ框架中指定 , 即使有接口 也可以使用cglib的方式进行代理 :
在主配置文件中的自动代理生成器中设置一个属性
<aop:aspectj-autoproxy proxy-target-class="true"/>
告诉框架要使用cglib动态
源清除工作的
---
#### @Pointcut : 定义和管理切入点的 (代替重复的切入点表达式)
如果你的项目中有多个切入点表达式是重复的,可以复用的,可以使用@Pointcut
属性 : value 切入点表达式
位置 : 自定义的方法上面
特点 :
- 当使用@Pointcut定义在一个方法上边 , 此时这个方法的名称就是切入点表达式的别名
- 其他通知中 , value属性就可以使用这个方法名称(**记得方法名后边要加括号**) , 代替切入点表达式了
---
### 有接口和没接口使用的代理方式 :
有接口使用的是JDK的动态代理
没有接口使用的是cglib动态代理 (内部实现是 : 使用继承的方法生成目标类的子类 , 继承之后 重写方法)
**在aspectJ框架中指定 , 即使有接口 也可以使用cglib的方式进行代理 :**
```xml
在主配置文件中的自动代理生成器中设置一个属性
<aop:aspectj-autoproxy proxy-target-class="true"/>
告诉框架要使用cglib动态