Aop切面编程详解

1、什么叫做面向切面编程(aop)及其应用场景
对比面向对象编程(oop),oop中模块化的核心单位是类,而aop的核心单位是切面。切面使事务管理等关注点的模块化得到实现。比如跨多个类型和对象的事务管理。登录token校验、方法执行的权限校验(访问控制)、性能检测、事务管理、日志记录等
2、切面编程的名词
切面(Aspetct):切点和通知的关系称为切面
连接点(Join Point):类中所有的方法都是连接点
切点(Point Cut):缺少共性功能代码的方法  (切入点一定是连接点,连接点不一定是切入点)
通知(Advice)(有的人叫增强):定义切面要干的事,被抽取的共性代码逻辑。(通知有位置区分)
    前置通知(before)
    后置通知(after)
    返回通知(after-returning)
    异常通知(after-throwing)
    环绕通知(around)
引入(Introduction):通知只能抽取共性逻辑代码,变量是抽取不出来的,需要把变量引入到切点方法中,就要用引入。引入机制可以成为类添加额外的成员变量或者成员方法。引入机制是在编译器或者类加载期完成的。(了解)
目标对象(Target Object):缺少了共性代码的对象 ,即被抽离共性的代码的对象(目标对象是不完整的)
代理对象(AOP Proxy):代理目标对象。
织入(weaving):在切入点地方,代理对象把通知织入到目标对象中,是一个动作(springAop在运行时织入 )
3、切面编程的相关注解和配置
@Aspect 定义切面
@Pointcut定义命名的切点
    切入点表达式:execution(* cn.tedu.store.service.impl.*.*(..))&& args(*)
            第一个*:返回任意类型
            第二个*:类
            第三个*:类中的方法
            第四个(..):方法中接收任意类型的参数
            第五个*:指定参数
              execution([方法的访问控制修饰符] 方法的返回值 包名.类名/接口名.方法名(参数)) 
              注意:方法的访问控制修饰符可以省略,写方法名的时候要把包名和类名全部带上。
@Before定义方法执行前通知  (应用场景:校验)
@After定义方法执行后通知    (应用场景:清理现场,释放资源)
@AfterReturning定义方法执行结果返回后通知    (应用场景:常规数据处理)
@AfterThrowing定义方法执行中抛出异常后通知    (应用场景:异常处理)
@Around 可以在目标方法的前后执行 通知    (应用场景:十分强大,可以做任何事情)
 


4、案例
4.1、切面获取目标方法参数的方式:
第一种:通过JoinPoint的JoinPoint.getArgs()方法进行获取,它是ProceedingJoinPoint的父类。
第二种:通过切入点表达式加上&& args(*)(耦合度太高,不推荐用)

4.2、获取目标方法的参数进行修改后传给目标方法,只能用ProceedingJoinPoint,也就只能用around通知方式

4.3、切面获取返回值的方式:
第一种: @AfterReturning(value = "pointCut()", returning = "res")    (returning 可以自定义,但是要与注解的方法参数名一致,即为Object res)
第二种:使用around通知,ProceedingJoinPoint.proceed()方法的返回值就是目标方法的返回值(注意:ProceedingJoinPoint返回目标对象的返回值的时候,返回方法类型要与目标方法返回类型一致)

4.4 、切面获取抛出异常信息 
 方式:@AfterThrowing(value = "pointCut()", throwing = "e") (throwing 可以自定义,但是要与注解的方式参数名一致,即为Throwable e)


5、aop代理
aop代理分为静态代理和动态代理,静态代理的代表是AspectJ,动态代理的代表是springAop。
所谓动态代理就是说aop框架不会去修改字节码,而是在内存中存在临时为方法生成一个Aop对象(我们可以把它称为“代理对象”),这个Aop对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

springAop中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理
5.1、JDK动态代理是通过反射来接收代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是IncocationHandler接口和Proxy类。现在我们都是面向接口编程,我们做的项目都是各种接口+实现类的方式。所以一个spring项目中有接口和实现类,如果不在spring配置文件中特殊配置的话(就是采用默认配置),默认的代理方式就是JDK动态代理。但是如果目标类没有实现接口,那么springAOP就会选择使用CGLIB来动态动力目标类。

5.2、CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,即在编译后,通过修改字节码,生成新的代理对象proxyObj。注意,CGLIB是通过继承的方式做的动态代理。因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。


举例:
JDK动态代理:
目标类(实现类)可以认作为厨师张三,张三能够具体实现接口的所有功能,比如老板可以让张三做饭,当aop代理的时候,会根据张三所实现的接口,再创造一个张三A(代理对象)作为张三的分身,这时候张三和张三A具有相同的接口(动态的体现),两者长得一模一样,这时候张三A就可以在张三做饭之前把菜洗干净了,然后张三本人来做饭。但是在老板(调用者)看来,自始至终都是张三(目标类)一个人在洗菜做饭。但是张三(目标类)知道,在他动手做饭之前他的代理对象帮他做了一些事情,同理代理对象也可以在他本人做晚饭以后帮他洗碗。
所以,目标类要是没有实现接口,程序就不能根据接口在实现出来一个代理对象,也就不能代替目标类(实现类)去做一些事情。这种代理方式只有在通过接口调用方法的时候才有效。

CGLIB动态代理:
我们把目标类比作李刚,代理的时候,程序会根据李刚制造一个子类来继承目标类,那么这个子类就是代理对象(李刚的儿子),所以李刚的儿子可以代替他爸收钱(因为他爸是李刚,哈哈),因为多态,所以程序识别不出来,然后目标类在替人办事的时候,在外人看来,就是李刚在收钱办事。但是李刚有很多特权,他儿子是没有权限的,也就是目标类中有final方法,子类是无法继承的,那么代理对象就不能代理这部分功能。(按理说final修饰的方式是可以被继承的,只是不能被重写。这里说CDLIB不能代理父类的这部分功能。猜想: 其实final修饰的方法时可以继承的,只是不能被重写,也就是不能子类具象化,em可以理解成别的方法可以直接被重写,然后通过重写的方法做切点增强,到了执行目标类的方法的时候再proceed()回去调用。所以它这个final方法无法找到切入点,那自然也无法对其增强。)


基于注解开发AOP:
不同注解之间的执行顺序是固定的:
around before
before
around after
after
afterReturning

扫描二维码关注公众号,回复: 12610585 查看本文章

相同注解之间的执行顺序是不固定的,受方法名和位置的影响,所以如果程序和执行顺序有关还是建议使用xml配置

JDK动态代理的前提是必须实现接口
jdk动态代理其实使用的是java.lang.reflect.Proxy类的newProxyInstance的方法实现动态代理

类似继承
CGLIB动态代理:全称(Code Generation Library),是一个代码生成的类库,可以动态生成字节码对象 
CGLIB实现动态代理步骤:
1、凭空生成一个字节码对象(空的)
2、设置字节码对象的父类是目标对象
3、通过生成的字节码对象进行回调方法,在回调的过程中进行目标方法的增强(方法功能的增强)
4、创建代理对象(完成动态代理)
调用代理对象的方法,方法功能就增强了

CGLIB是第三方的,但是已经整合到spring的核心jar包中了。

JDK动态代理(对对象进行代理)
CGLIB动态代理(对(类)字节码进行代理)

idea查看一个接口的子接口或者实现类的快捷键:
先选中类或者接口,再按从Ctrl + h
 

猜你喜欢

转载自blog.csdn.net/D_J1224/article/details/108362494
今日推荐