Spring之理解AOP(面向切面)


一、AOP常用的术语

  1. 切面:就是那些重复的、公共的、通用的功能称为切面,例如:日志、事务、权限
  2. 连接点:就是目标方法。因为在目标方法中要实现目标方法的功能和切面功能。
  3. 切入点:指定切入点的位置。多个连接点构成切入点。切入点可以是一个目标方法,可以是一个类中的所有方法,可以是某个包下的所有类中的方法。
  4. 目标对象:操作谁,谁就是目标对象
  5. 通知:来指定切入的时机。是在目标方法执行前还是执行后还是出错时,还是环绕目标方法 切入切面功能。

二、AspectJ框架

AspectJ是一个优秀面向切面的框架,它扩展了Java语言,提供了强大的切面实现。

2.1 AspectJ常见的通知类型

@Before:前置通知
@AfterReturning后置通知
@Around:环绕通知
@After:最终通知
@Poincut:定义切入点

2.2 AspectJ的切入点表达式

规范的公式:
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
简化后的公式:
execution(方法返回值 方法声明(参数))
用到的符号:

  1.  * 代表任意的字符
    
  2. ..如果出现在方法的参数中,则代表任意参数。如果出现在路径中,则代表本路径及其所有的子路径
    

示例:
3. execution(public * (…)) 只要是public访问权限下的、任意返回值类型的、任意路径下的、任意方法的任意参数。也就是公共访问权限下的所有方法都可切入切面功能
4. execution(
set*(…)) 所有以set开头的方法
5. execution(* com.xyz.service.impl..(…) ) impl包下的任意类的任意方法的任意参数

2.3 AspectJ的前置通知@Before

在目标方法执行钱切入切面的功能。在切面方法中不可获得目标方法的返回值,只能得到目标方法的签名。
在这里插入图片描述
实现步骤:

  1. 创建业务接口
/**
 7. 业务接口
 */
public interface SomeService {
    String doSome(String name,int age);
}
  1. 创建业务实现
/**
 * 业务实现类
 */
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome的业务功能实现....."+name);
        return "cccccccccc";
    }
}
  1. 创建切面类,实现切面方法
/**
 * 此类为切面类,包含各种切面方法
 */
@Aspect  //在pom.xml文件中添加依赖。交给AspectJ的框架去识别切面类
public class MyAspect {
    @Before(value = "execution(public String com.bjpowernode.SomeServiceImpl.doSome(String,int))")
    public void before(){
        System.out.println("切面方法中的前置功能实现......");
    }
}
  1. 在applicationContext.xml文件中进行配置
<!--创建业务对象-->
    <bean id="someService" class="com.bjpowernode.SomeServiceImpl"></bean>
    <!--创建切面对象-->
    <bean id="aspect" class="com.bjpowernode.MyAspect"></bean>
    <!--绑定-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  1. 测试类
    @Test
    public void test1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        SomeService service = (SomeService) ac.getBean("someService");
        String s = service.doSome("西西",22);
        System.out.println(s);
    }
  1. 运行结果
    在这里插入图片描述
    注意:AspectJ框架切换JDK动态代理和CGLib动态代理:
  •  <aop:aspectj-autoproxy></aop:aspectj-autoproxy>===》默认是JDK动态代理,取出对象时必须使用接口类型。
    
     <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>===》设置为CGLib动态代理,取出对象时,可以使用接口和实现类来接
    
    使用接口来接永远不会出错
    使用注解的方式实现AspectJ的前置功能:
  1. 创建业务接口
/**
 7. 业务接口
 */
public interface SomeService {
    String doSome(String name,int age);
}
  1. 创建业务实现
/**
 * 业务实现类
 */
@Service
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome的业务功能实现....."+name);
        return "cccccccccc";
    }
}
  1. 创建切面类,实现切面方法
/**
 * 此类为切面类,包含各种切面方法
 */
@Aspect  //在pom.xml文件中添加依赖。交给AspectJ的框架去识别切面类
@Component
public class MyAspect {
    @Before(value = "execution(public String com.bjpowernode.SomeServiceImpl.doSome(String,int))")
    public void before(){
        System.out.println("切面方法中的前置功能实现......");
    }
}
  1. 在applicationContext.xml文件中进行配置(基于注解的访问要添加包扫描)
<context:component-scan base-package="com.bjpowernode"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  1. 测试类(注意此时测试类中getBean的参数
    @Test
    public void test1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        SomeService service = (SomeService) ac.getBean("someServiceImpl");
        String s = service.doSome("西西",22);
        System.out.println(s);
    }

2.4 AspectJ的后置通知@ArterReturning

在这里插入图片描述
使用注解方式:
实现步骤:

  1. 创建业务接口(同上)
  2. 创建业务实现(同上)
  3. 创建切面类,实现切面方法
@Aspect  //在pom.xml文件中添加依赖。交给AspectJ的框架去识别切面类
@Component
public class MyAspect {
    //returning:指定目标方法的返回值名称,名称必须与切面方法的参数名称一致
    //value:指定切入点表达式
    @AfterReturning(value = "execution(* com.bjpowernode.*.*(..))",returning = "obj")
    public void myafterReturning(Object obj){
        System.out.println("后置通知功能实现....");
    }
}
  1. 在applicationContext.xml文件中进行配置(基于注解的访问要添加包扫描)(同上)
  2. 测试类(同上)
  3. 运行结果
    在这里插入图片描述
    拓展:操作目标方法的返回值
    @AfterReturning(value = "execution(* com.bjpowernode.*.*(..))",returning = "obj")
    public void myafterReturning(Object obj){
        System.out.println("后置通知功能实现....");
        if(obj != null){
            if(obj instanceof String){
                String s  = obj.toString().toUpperCase();//改为大写
                System.out.println("在切面方法中目标方法的返回值:"+s);
            }
        }
    }

运行结果:
在这里插入图片描述
结果是并没有将目标方法的返回值进行改变
再举一个例子:
1、添加一个学生实体类:

public class Student {
    private String name;

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student(String name) {
        this.name = name;
    }
}

2、在业务接口中添加一个方法:

public interface SomeService {
    String doSome(String name,int age);
    Student change();
}

3、在实现类中重写change()方法:

 @Override
    public Student change() {
        System.out.println("change方法被执行");
        return new Student("张三");
    }

4、在切面中修改目标方法的返回值:

 @AfterReturning(value = "execution(* com.bjpowernode.*.*(..))",returning = "obj")
    public void myafterReturning(Object obj){
        System.out.println("后置通知功能实现....");
        if(obj != null){
            if(obj instanceof String){
                obj  = obj.toString().toUpperCase();//改为大写
                System.out.println("在切面方法中目标方法的返回值:"+obj);
            }
            if(obj instanceof Student){
                Student stu = (Student) obj;
                stu.setName("李四");
                System.out.println("在切面方法中目标方法的返回值:"+stu);
            }
        }
    }

5、测试:

 @Test
    public void test2(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        SomeService service = (SomeService) ac.getBean("someServiceImpl");
        Student stu = service.change();
        System.out.println("目标方法的返回值:"+stu);
    }

6、运行结果:
在这里插入图片描述
总结:
如果目标方法的返回值是8种基本数据类型或String类型则不可改变
如果是引用型数据类型则可以改变

2.5 AspectJ的环绕通知@Around

是通过拦截目标的方式,在目标方法前后增强功能的通知。是功能最强大的通知,一般事务使用此通知。它可以轻易的改变目标方法的返回值。
在这里插入图片描述
使用注解方式:
实现步骤(没有添加学生实体类,没有添加方法):

  1. 创建业务接口(同上)
  2. 创建业务实现(同上)
  3. 创建切面类,实现切面方法
    @Around(value = "execution(* com.bjpowernode.*.*(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        //前切功能实现
        System.out.println("环绕通知中的前切功能实现");
        //目标方法实现
        Object obj = pjp.proceed(pjp.getArgs());
        //后切功能实现
        System.out.println("环绕通知中的后切功能实现");
        return obj.toString().toUpperCase();//改变了目标方法的返回值
    }
  1. 测试类(同上)
  2. 运行结果:
    在这里插入图片描述

2.6 AspectJ的最终通知@After

无论目标方法是否正常执行,最终通知的代码都会被执行(类似于try-catch中的finally)。
使用注解方式:
实现步骤(没有添加学生实体类,没有添加方法):

  1. 创建业务接口(同上)
  2. 创建业务实现(同上)
  3. 创建切面类,实现切面方法
    @After(value = "execution(* com.bjpowernode.*.*(..))")
    public void myAfter(){
        System.out.println("最终通知的功能实现了....");
    }
  1. 测试类(同上)
  2. 运行结果:
    在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值