AOP的概念和使用

Spring有两个核心概念,一个是IOC/DI,另一个是AOP

AOP:面向切面编程(Aspect Oriented Programing)

OOP:面向对象编程(Object Oriented Programing)

AOP功能:在不惊动原始设计的基础上增强代码,符合Spring的无入侵式/无侵入式思想

        

 的案例中BookServiceImpl中有save , update , delete和select方法,这些方法我们给起了一 个名字叫连接点

四个方法中,update和delete只有打印没有计算万次执行消耗时间, 但是在运行的时候已经有该功能,那也就是说update和delete方法都已经被增强,所以对于需要增 强的方法我们给起了一个名字叫切入点

通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,那哪个切入点需要添 加哪个通知,就需要提前将它们之间的关系描述清楚,那么对于通知和切入点之间的关系描述,我们 给起了个名字叫切面

通知是一个方法,方法不能独立存在需要被写在一个类中,这个类我们也给起了个名字叫通知类

总结

在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法

一个具体的方法:如com.itheima.dao包下的BookDao接口中的无形参无返回值的save方 法 匹配多个方法:

所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意 方法,所有带有一个参数的方法

AOP实例

使用注解开发

1.导入坐标

在pom.xml文件中导入相关坐标

2.制作连接点(原始操作,dao接口,实现类)

添加bookdao和bookdaoimpl类

public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save ...");
    }
    public void update(){
        System.out.println("book dao update ...");
}
}

 创建Spring配置类

编写APP运行类

3.制作共性功能(通知类和通知)

通知就是将共性功能抽取出来后形成的方法,共性功能指的就是当前系统时间的打印。


@Component    //通知类必须配置成Spring管理的bean
@Aspect    //设置当前类为切面类类
public class MyAdvice {
    //设置切入点,要求配置在方法上方
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    //设置在切入点pt()的前面运行当前操作(前置通知)
    // @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

4.定义切入点

通过@Pointcut定义切入点,且依赖一个不具有实际意义的方法(pt())进行,配置在pt()方法上方,

5.绑定切入点与通知关系(制作切面)

通过@Before绑定切入点和通知的关系,设置在切入点的前面运行操作(前置)

6.开启注解格式AOP功能

@EnableAspJAutoproxy

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

 

 AOP工作流程

1.容器启动

2.读取所有切面配置中的切点

3.初始化bean

此时要被实例化的bean对象类中的方法和切入点进行匹配

 

匹配成功就,创建原始对象的代理对象,匹配成功说明需要对其进行增强

匹配失败,创建原始对象

对哪个类做增强,这个类对应的对象就叫做目标对象

因为要对目标对象进行功能增强,而采用的技术是动态代理,所以会为其创建一个代理对象

最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强

4.获取bean执行方法

获取的bean是原始对象时,调用方法并执行,完成操作

获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

结论:容器中存入的是目标对象的代理对象

AOP的两个核心概念

目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终 工作的

代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实 现

上面这两个概念比较抽象,简单来说, 目标对象就是要增强的类[如:BookServiceImpl类]对应的对象,也叫原始对象,不能说它不能运 行,只能说它在运行的过程中对于要增强的内容是缺失的。 SpringAOP是在不改变原有设计(代码)的前提下对其进行增强的,它的底层采用的是代理模式实现 的,所以要对原始对象进行增强,就需要对原始对象创建代理对象,在代理对象中的方法把通知 [如:MyAdvice中的method方法]内容加进去,就实现了增强,这就是我们所说的代理(Proxy)。

AOP切入点表达式 

1.语法格式

切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常 名)

//通过以下的例子来理解格式
execution(public User com.itheima.service.UserService.findById(int))

2.通配符

execution(void com.itheima.dao.BookDao.update())
//匹配接口,能匹配到
execution(void com.itheima.dao.impl.BookDaoImpl.update())
//匹配实现类,能匹配到
execution(* com.itheima.dao.impl.BookDaoImpl.update())
//返回值任意,能匹配到
execution(* com.itheima.dao.impl.BookDaoImpl.update(*))
//返回值任意,但是update方法必须要有一个参数,无法匹配,要想匹配需要在update接口和实现类添加参数
execution(void com.*.*.*.*.update())
//返回值为void,com包下的任意包三层包下的任意类的update方法,匹配到的是实现类,能匹配
execution(void com.*.*.*.update())
//返回值为void,com包下的任意两层包下的任意类的update方法,匹配到的是接口,能匹配
execution(void *..update())
//返回值为void,方法名是update的任意包下的任意类,能匹配
execution(* *..*(..))
//匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
execution(* *..u*(..))
//匹配项目中任意包任意类下只要以u开头的方法,update方法能满足,能匹配
execution(* *..*e(..))
//匹配项目中任意包任意类下只要以e结尾的方法,update和save方法能满足,能匹配
execution(void com..*())
//回值为void,com包下的任意包任意类任意方法,能匹配,*代表的是方法
execution(* com.itheima.*.*Service.find*(..))
//将项目中所有业务层方法的以find开头的方法匹配
execution(* com.itheima.*.*Service.save*(..))
//将项目中所有业务层方法的以save开头的方法匹配

 3.书写技巧

AOP通知类型

前置通知

后置通知

环绕通知(重点)

返回后通知(了解)

抛出异常后通知(了解)

 前置通知:@Before

@Pointcut定义任意一个切入点,在update()方法上,此时update()方法就是切入点,切点依赖pt()方法

@Before表示前置通知,@Before("pt()")表示切入点和通知已经绑定,通知在切入点之前运行通知

后置通知@After

@Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
@After("pt()")    //表示在切入点之后运行该通知
    public void after() {
        System.out.println("after advice ...");
    }

环绕通知@Around()

使用环绕通知,需要对原始操作进行增强,因为环绕通知需要在原始方法的前后进行增强

@Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
@Around("pt()")
    public void around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        pjp.proceed();
        System.out.println("around after advice ...");
    }

 原始方法中存在返回值的处理时,例如

public int select(){
        System.out.println("book dao select is running");
        return 100;
    }

@Pointcut("execution(int com.itheima.dao.BookDao.select())")
    private void pt2(){}


@Around("pt2()")
    public void around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        pjp.proceed();
        System.out.println("around after advice ...");
    }


int num = bookDao.select();
        System.out.println(num);

运行出现错误,空的返回不匹配原始方法的int返回 

 void就是返回Null 原始方法就是BookDao下的select方法

所以如果我们使用环绕通知的话,要根据原始方法的返回值来设置环绕通知的返回值,具体解决方案 为:


@Around("pt2()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("around before advice ...");
    //表示对原始操作的调用
    Object ret = pjp.proceed();
    System.out.println("around after advice ...");
    return ret;
}

object类型比int类型更通用 

返回后通知@AfterReturning

异常后通知@AfterThrowing

 

 1.环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法 调用前后同时添加通知

2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行

3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为 Object类型

4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成 Object

5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值