AOP面向切面编程
目录
一、AOP概述1. AOP简介AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程 可以看之前写的动态代理, https://www.cnblogs.com/mengd/p/13429797.html
动态代理的作用:
2. 如何理解AOPAOP(Aspect Orient Programming)面向切面编程
理解:
二、AOP编程术语1. 切面(Aspect)表示增强的功能, 就是一堆代码,完成某个一个功能,非业务功能 常见的切面功能有日志, 事务, 统计信息, 参数检查, 权限验证 2. 连接点(JoinPoint)连接业务方法和切面的位置,就某类中的业务方法 3. 切入点(Pointcut)指多个连接点方法的集合,多个方法 4. 目标对象(Target)给哪个类的方法增加功能, 这个类就是目标对象 5. 通知(Advice)通知表示切面功能执行的时间 一个切面有三个关键的要素:
三、Aspectj对AOP的实现aop是一个规范,是动态的一个规范化,一个标准 aop的技术实现框架:
aspectJ框架实现aop有两种方式:
1. Aspectj的通知类型AspectJ 中常用的通知有五种类型
2. Aspectj的切入点表达式
以上表达式共4个部分
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就 注意,表达式中黑色文字表示可省略部分,各部分间用空格分开 在其中可以使用以下符号:
常用的几个:
指定切入点的位置:任意的公共方法
指定切入点的位置:任何一个以set开始的方法
指定切入点的位置:定义在service包里的任意类的任意方法
指定切入点的位置:定义在service包或者子包里的任意类的任意方法
指定所有包下的service子包下所有类中所有的方法为切入点 3. Aspectj的开发环境1. maven依赖
2. 引入AOP约束 在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的 四、AspectJ基于注解的AOP实现1. 实现步骤1. 定义业务接口与实现类 @H_502_290@package com.md.b1;
/**
* @author MD
* @create 2020-08-09 10:55
*/
public interface SomeService {
void doSome(String name,Integer age);
}
//-------------------------
package com.md.b1;
/**
* @author MD
* @create 2020-08-09 10:55
*/
// 目标类
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
// 给doSome方法增加一个功能,在执行之前输出时间
System.out.println("目标方法doSome()");
}
}
2. 定义切面类 类中定义了若干普通方法,将作为不同的通知方法,用来增强功能 注意点:
定义方法,方法是实现切面功能的 方法的要求:
@H_502_290@package com.md.b1;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @author MD
* @create 2020-08-09 10:58
*/
@Aspect
public class MyAspect {
// 前置通知,具体的在下变
@Before(value = "execution(public void com.md.b1.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
// 就是你切面要执行的功能代码
System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date());
}
}
3. 定义目标对象切面类对象 还是在src/main/resources下建立applicationContext.xml 整体结构如下:
4. 注册AspectJ自动代理 在上面文件的基础上添加
<aop:aspectj-autoproxy/>的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。 5. 测试类中的使用 @H_502_290@package com.md;
import com.md.b1.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import sun.security.provider.Sun;
/**
* @author MD
* @create 2020-08-09 15:28
*/
public class MyTest01 {
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 从容器中获取目标对象,此时的目标对象是经过了aspectj修改后的代理对象
SomeService proxy = (SomeService) ac.getBean("someService");
//com.sun.proxy.$Proxy8 jdk动态代理
//System.out.println(proxy.getClass().getName());
// 通过代理的对象执行方法,实现目标方法执行,增强了功能
proxy.doSome("张三",19);
// 前置通知,切面功能:在目标方法之前输出时间:Sun Aug 09 15:33:24 CST 2020
// 目标方法doSome()
}
}
2. @Before前置通知在目标方法执行之前执行 被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式 通过该参数,可获取切入点表达式、方法签名、目标对象等
@H_502_290@@Aspect
public class MyAspect {
// @Before():前置通知注解
// 属性:value 是切入点表达式,表示切面功能执行的位置
// 位置:在方法的上面
// 特点:
// 1. 在目标方法之前执行
// 2. 不会改变目标方法的执行结果
// 3. 不会影响目标方法的执行
// @Before(value = "execution(public void com.md.b1.SomeServiceImpl.doSome(String,Integer))")
// public void myBefore(){
// // 就是你切面要执行的功能代码
// System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date());
// }
// @Before(value = "execution( * *..SomeServiceImpl.do*(..))")
// public void myBefore(){
// // 就是你切面要执行的功能代码
// System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date());
// }
/**
* 指定通知方法中的参数 : JoinPoint
* JoinPoint:业务方法,要加入切面功能的业务方法
* 作用是:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参。
* 如果你的切面功能中需要用到方法的信息,就加入JoinPoint.
* 这个JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数
*/
@Before(value = "execution(void *..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);
}
// 方法的定义:void com.md.b1.SomeService.doSome(String,Integer)
// 方法的名称:doSome
// 参数:张三
// 参数:19
// 就是你切面要执行的功能代码
System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date());
}
}
3. @AfterReturning后置通知在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值 该注解的 returning 属性就是用于指定接收方法返回值的变量名的 所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型 增加接口的方法 @H_502_290@public interface SomeService {
void doSome(String name,Integer age);
String doOther(String name,Integer age);
}
//--------------------------------------------------
// 目标类
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
// 给doSome方法增加一个功能,在执行之前输出时间
System.out.println("目标方法doSome()");
}
@Override
public String doOther(String name,Integer age) {
System.out.println("目标方法doOther()");
return "a";
}
}
定义切面 @H_502_290@@Aspect
public class MyAspect {
/**
* 后置通知定义方法,方法是实现切面功能的。
* 方法的定义要求:
* 1.公共方法 public
* 2.方法没有返回值
* 3.方法名称自定义
* 4.方法有参数的,推荐是Object ,参数名自定义
*/
/**
* @AfterReturning:后置通知
* 属性:1.value 切入点表达式
* 2.returning 自定义的变量,表示目标方法的返回值的。
* 自定义变量名必须和通知方法的形参名一样。
* 位置:在方法定义的上面
* 特点:
* 1。在目标方法之后执行的。
* 2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
* Object res = doOther();
* 3. 可以修改这个返回值,但不影响最后的调用结果
*
* 后置通知的执行
* Object res = doOther();
* 参数传递: 传值, 传引用
* myAfterReturing(res);
* System.out.println("res="+res)
*
*/
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")
public void myAfterReturing(Object res){
if (res.equals("a")){
// 你可以做一些功能
System.out.println("登陆成功");
}else{
System.out.println("登陆失败");
}
// Object res:是目标方法执行后的返回值,可以根据返回值做切面功能处理
System.out.println("后置通知:获取的返回值是:"+res);
// 修改目标方法的返回值,是否影响最后的方法调用结果
// 无影响,
if (res != null){
res = "hello Aspectj";
}
}
}
4. @Around环绕通知在目标方法执行之前之后执行。 被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。 接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。 若目标方法有返回值,则该方法的返回值就是目标方法的返回值。 最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行 首先增加方法和实现 @H_502_290@public interface SomeService {
void doSome(String name,Integer age);
String doFirst(String name,Integer age);
}
//-------------------------------------------
// 目标类
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
// 给doSome方法增加一个功能,在执行之前输出时间
System.out.println("目标方法doSome()");
}
@Override
public String doOther(String name,Integer age) {
System.out.println("目标方法doOther()");
return "a";
}
@Override
public String doFirst(String name,Integer age) {
System.out.println("目标方法doFirst()");
return "doFirst";
}
}
切面类 @H_502_290@@Aspect
public class MyAspect {
/**
* 环绕通知方法的定义格式
* 1.public
* 2.必须有一个返回值,推荐使用Object
* 3.方法名称自定义
* 4.方法有参数,固定的参数 ProceedingJoinPoint
*/
/**
* @Around: 环绕通知
* 属性:value 切入点表达式
* 位置:在方法定义的上面
* 特点:
* 1.它是功能最强的通知
* 2.在目标方法的前和后都能增强功能。
* 3.控制目标方法是否被调用执行
* 4.修改原来的目标方法的执行结果。 影响最后的调用结果
*
* 环绕通知,等同于jdk动态代理的,InvocationHandler接口
*
* 参数: ProceedingJoinPoint 就等同于 Method
* 作用:执行目标方法的
* 返回值: 就是目标方法的执行结果,可以被修改。
*
* 环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务
*/
// @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
// public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//
// Object result = null;
//
//
// System.out.println("环绕通知,在目标方法之前加入通知:现在时间:"+new Date());
//
// // 1. 目标方法调用,等同于method.invoke(); 在这里等同于Object result = doFirst();
// result = pjp.proceed();
//
// // 2. 在目标方法前后加入功能
// System.out.println("环绕通知,在目标方法之后提交事务");
//
// // 3. 返回目标方法的执行结果
// return result;
//
// }
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
// 可以获取到调用方法的参数
String name = "";
// 获取第一个参数的值
Object args[] = pjp.getArgs();
if (args!=null && args.length > 1){
Object arg = args[0];
name = (String) arg;
}
// 实现环绕通知
Object result = null;
System.out.println("环绕通知,在目标方法之前加入通知:现在时间:"+new Date());
// 1. 目标方法调用,等同于method.invoke(); 在这里等同于Object result = doFirst();
if ("张三".equals(name)){
// 符合条件,调用目标方法
result = pjp.proceed();
}
// 2. 在目标方法前后加入功能
System.out.println("环绕通知,在目标方法之后提交事务");
// 还可以修改目标方法的执行结果,影响方法最后的调用结果
if (result != null){
result = "修改了";
}
// 3. 返回目标方法的执行结果
return result;
}
}
测试: @H_502_290@public class MyTest03 {
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 从容器中获取目标对象
SomeService proxy = (SomeService) ac.getBean("someService");
// 通过代理的对象执行方法,实现目标方法执行,增强了功能
String str = proxy.doFirst("张三",20);
System.out.println(str);
}
}
//环绕通知,在目标方法之前加入通知:现在时间:Mon Aug 10 20:52:07 CST 2020
// 目标方法doFirst()
// 环绕通知,在目标方法之后提交事务
// 修改了
5. @AfterThrowing 异常通知在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象 相当于try-catch中catch里面执行的 增加业务方法 @H_502_290@public interface SomeService {
void doSome(String name,Integer age);
void doSecond();
}
//实现类-----------------------------------------
@Override
public void doSecond() {
System.out.println("执行业务方法doSecond()" + (10/0));
}
切面类: @H_502_290@@Aspect
public class MyAspect {
/**
* 异常通知方法的定义格式
* 1.public
* 2.没有返回值
* 3.方法名称自定义
* 4.方法有个一个Exception, 如果还有是JoinPoint,*/
/**
* @AfterThrowing:异常通知
* 属性:1. value 切入点表达式
* 2. throwinng 自定义的变量,表示目标方法抛出的异常对象。
* 变量名必须和方法的参数名一样
* 特点:
* 1. 在目标方法抛出异常时执行的
* 2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。
* 如果有异常,可以发送邮件,短信进行通知
*
* 执行就是:
* try{
* SomeServiceImpl.doSecond(..)
* }catch(Exception e){
* myAfterThrowing(e);
* }
*/
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
public void myAfterThrowing(Exception ex) {
System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());
//发送邮件,短信,通知开发人员
}
}
6. @After最终通知无论目标方法是否抛出异常,该增强均会被执行 相当于try-catch-finally中finally里面执行的 增加方法及实现 @H_502_290@public interface SomeService {
void doSome(String name,Integer age);
void doSecond();
void doThird();
}
//------------------------
@Override
public void doThird() {
System.out.println("执行业务方法doThird()"+ (10/0));
}
切面类 @H_502_290@@Aspect
public class MyAspect {
/**
* 最终通知方法的定义格式
* 1.public
* 2.没有返回值
* 3.方法名称自定义
* 4.方法没有参数, 如果还有是JoinPoint,*/
/**
* @After :最终通知
* 属性: value 切入点表达式
* 位置: 在方法的上面
* 特点:
* 1.总是会执行
* 2.在目标方法之后执行的
*
* try{
* SomeServiceImpl.doThird(..)
* }catch(Exception e){
*
* }finally{
* myAfter()
* }
*
*/
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
//一般做资源清除工作的。
}
}
7. @Pointcut定义切入点当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式 其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法
五、总结前面的概念有些绕,看了代码就比较清晰了,感觉和python的装饰器很像,只不过py的没有这么绕,就是为已经存在的对象添加额外的功能 总的来说就是在一个方法前或一个方法后执行一些通用的方法,提高效率,把那些业务的方法写一块,那些非业务的方法或那些业务方法经常使用的方法写成切面类,使用方便还便于管理 1. 使用aspectj框架实现aop使用aop:目的是给已经存在的一些类和方法增加额外的功能,前提是不改变原来类的代码
2. Review
强制使用cglib代理 (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |







