前言
本文有TODO啊。
本文并不会具体写Spring aop的api之类的,大部分为了理解Spring AOP的原理。
如果要看使用的话可以随便看一下网上随便搜的
aop简介
AOP(Aspect Oriented Programming),面向切面编程。
- 简单来说,就是在执行某项事务前或后要执行通用的操作,有的人可能会说直接复制黏贴不就好了,那如果有许多事务都要求有通用的操作呢?(如:打印日志)这会让代码十分冗余,而且一旦要修改通用功能,每一个事务都要修改,这显然违背了我们的开闭原则 (开放扩展,关闭修改)。这也就引出了aop的思想,在讲解aop之前我们需要先了解代理。
代理模式
代理(Proxy)模式,用户不直接访问目标对象,而由其他对象间接访问。
举个例子,这就和买房一样,你并不会直接和房地产商直接沟通,而是和经销商交涉交易。
虽然我们没和目标直接接触,但事实上目标已经执行了(卖房),而经销商也从这以过程中进行了操作(办理手续等)。
下面来看一下实际的代码吧
静态代理
在web开发中,我们常常由多个Dao,现在由个UserDao的接口,如下
1 | public interface UserDao { |
UserDaoImpl实现了接口
1 | class UserDaoImpl implements UserDao{ |
此时,我想在dao操作前开启事务,执行后关闭事务,假设有很多操作都要这样。如下
1 | public void save() { |
上面的代码是不是很恶心,全部都是重复代码。这时我们就要创建一个代理来帮助我们。我们要注意两个点
- 代理要和userDao用一样的方法
- 代理只是帮你增强userDao的方法,实际关键点还是UserDao
明白了上面两点我们就可以来写代理类了,
1 | static class UserDaoProxy implements UserDao { |
我们可以通过代理来用操作啦
1 | public static void main(String[] args) { |
这就是人们常说的静态代理了。
不过仔细看一下这些代码,接口一改,代理也要改,接口一多,代理也多,代理和接口的耦合度也太高了吧。
不要怕,jdk帮我们想好出路了!!就决定时你了!动态代理!
动态代理
动态代理的优点:
- 代理类不用实现接口,减少了代理类
- JDKAPI会动态生成代理对象(根据我们指定的目标对象,代理对象,实现接口的类型)
下面我们来看一下如何实现的吧。
JAVA的API中提供了Proxy类,用它的newInstance方法就可以获得目标的代理对象。
newInstance方法有三个参数
- loader,代理对象的类装载器
- interfaces,目标对象的接口
- handler,用于增强方法
由上面的三个参数可以推测出
- 因为有目标对象的接口,所以代理对象拥有和目标对象一样的方法
- 代理对象的方法实际都是handler执行的
目标对象
乔尼·乔斯达是JoJo家族的人,会替身和波纹。
1 | static class Johnny implements JoJo { |
接口
public interface JoJo {
void stand();
void bowen();}
代理类
1 | static class JohnnyProxy { |
测试
1 | public static void main(String[] args) { |
我们对比一下静态代理和动态代理:
静态代理
- 1.代理类要实现目标的接口,并实现方法,所以修改接口,代理类也要随之修改。
- 2.代理类要预先创建
动态代理
- 1.代理类不用实现目标接口,改接口后不用改代理类(因为反射都帮你把方法和参数都自动写了)
- 2.JDK会动态的帮你生产所需的代理对象(反射嘛)
- 3.目标对象一定要有接口
总结一下,代理模式的作用,就是增强方法,在原方法的前后都可以添加操作。
休息一下,然后看看Spring中的aop是怎么实现的吧。
Spring中的aop
cglib代理简介
回忆一下,我们因为静态代理需要实现目标的接口,一旦修改接口,代理类也要修改。
所以我们学了动态代理,但动态代理也不是完美的,它需要目标对象拥有接口。
cglib代理又叫做子类代理,从内存中构建一个子类进目标对象进行增强。
编写cglib代理
在编写cglib代理前要注意一下几点
代理的类为final会报错,因为cglib就是构建子类进行增强的,目标为final就不能继承了。
若目标对象的方法时final,则不会对该方法进行增强。
编写cglib代理需要引入cglib包,cblib.jar已经在spring的核心包中有了,所以我们只要导入Spring-core-x.x.x.jar即可。
cglib包是一个高性能的代码生成包,它可以在运行时扩展类和实现接口。。
它在许多AOP框架中使用,如Spring AOP(interception)。实现如下:
1 | public class ProxyFactory implements MethodInterceptor{ |
- 测试如下:
1 |
|
手撕基于代理的Spring AOP
aop面向切面编程,全称:aspect object programming 面向切面编程。
- 功能: 让业务代码和重复的代码分离。
看下面的代码
1 | public void save() { |
是不是发现和上面学代理的时候情况很像,没错,我就是照抄的233。
- 这里我们为了方便我们把公共部分需要重复编写的代码统称为切面,把重点的业务称作切点,把切点放入切面中称作织入。
厨师在做三文治的时候是要吐司(切面)和馅料(切点),客人点单后,厨师们动态地把馅料,放在吐司中间(织入)。
注意,这里吐司和馅料都可以随便换的,比如我想吃黑麦肉松三文治(黑麦吐司+肉松),你想吃黑麦火腿三文治(黑麦吐司+火腿)。Spring aop 做的事事情就和做三文治一样的,你提供一个切面,和切点,它帮你进行织入。
下面我们就来自己实现一下Spring aop 吧。
这种对方法的增强,肯定是我代理的天下啦。这里我们使用cglib
- 把切面的功能封装起来
1 | public class AOP { |
- 创建代理
1 | public class ProxyFactory2 implements MethodInterceptor { |
- 测试
1
2
3
4
5
6
7
8
9
public void test() {
//这里直接new了,spring的话注入就完事了
UserDao ud = new UserDao();
AOP aop = new AOP();
//指定切面和切入点
UserDao proxy = (UserDao) new ProxyFactory2(aop,ud).getProxyInstance();
proxy.save();
}
- 上面我们是基于cglib代理进行实现的aop,实际上Spring AOP有两套实现方式,我们常用的是Spring AspectJ
- 基于代理的经典SpringAOP(advisor)
- 在这种方式下Spring AOP 默认使用的是JDK动态代理,若目标类没有接口则用cglib代理。上面的代码就是简化版的
- 基于AspectJ的AOP(aspect)
- Spring借鉴了AspectJ的做法,但Spring AOP本质上还是动态代理。所以Spring AOP不需要专门的编辑器
- 基于代理的经典SpringAOP(advisor)
AspectJ是语言级别的AOP实现,扩展了Java语言,定义了AOP语法,能够在编译期提供横切代码的织入,所以它有专门的编译器用来生成遵守Java字节码规范的Class文件(Spring aop是在运行时织入)
Spring AspectJ
在讲Spring AspectJ之前我们需要先补充两个专业名词:
通知(Advice):表示添加到切点的代码,并携带了具体位置信息(前置、后置、返回、异常、环绕)。
引入/引介(Introduction):向现有的类添加新方法或属性,是一种特殊的增强。
Spring 给我们提供了更强大的aop,它能够指定单体某个方法作为切点,并用通知(Advice)来确定织入的具体位置(如切点的前置、后置、返回等)。
基本的注解和xml随便找个博客就能抄了,很简单就不写了。
使用引入/引介实现为Bean新增方法
现在我们有Student和Teacher两个接口及其实现类如下,
现在小明想要造反了,想要改试卷,问题是小明不可能成为老师的(不能实现teacher接口),这可咋整呢。这时候就可以使用引入切面了,让代理来帮助小明“改卷”,
- 创建切面类
1 |
|
当你写完并引入这个切面的时候,spring会自动帮XiaoMing实现Teacher接口。
只要强转一下,小明也可以改试卷啦!
。。。
然而事实是这样的
- 当引入接口方法被调用时,代理对象会把此调用委托给实现了新接口的某个其他对象。实际上,一个Bean的实现被拆分到多个类中。
- 也可以这么理解,在代理类中,我们有两个对象。
总结与注意事项
其实AOP还有很多很多的知识点,但大致上就是上面那些了。
- AOP的底层就是动态代理(JDK代理和cglib代理),是运行是的增强
- 使用单例的话,最好使用CGLib代理,因为CGLib代理对象运行速度要比JDK的代理对象要快