1.AOP是什么
面向切面编程,他是对面向对象的一种补充。
2.AOP有啥用
AOP 有助于我们将不同但是有必要的重复性代码重构为不同的模块。这么做的好处是,我们可以将这些重复性代码集中管理起来复用,而不是每次都要重复写一遍。
这种方法的好处是,代码将会变得更易于维护,从而将业务逻辑从杂乱的代码中脱离出来,专注于业务逻辑代码的开发。我们将这些不同的功能划分到不同的切面中。
也就是,图省事
3.术语
1.通知(Advice)
你想要使用的功能,比如 安全,事物,日志等。
2.连接点(JoinPoint)
方法的前后,抛出异常的地方,都是连接点。
3.切入点(Pointcut)
连接点可以来进行通知,但是不是所有的连接点都是有用的,那么就需要用切点来定义需要通知的方法,用切点来筛选连接点。
4.切面(Aspect)
通知:干啥,什么时候干
切点:在哪干
通知+切点=切面
5.引入(introduction)
允许我们向现有的类添加新方法属性。就是把切面用到目标类中。
6.目标(target)
引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
7.代理(proxy)
通过代理才能实现AOP,详情看下面的好兄弟和好儿子。
8.织入(weaving)
把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。
关键就是:切点定义了哪些连接点会得到通知
AOP实现原理
spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用伪装类,伪装类中就先执行了切面,再把调用转发给真正的目标bean。
那么问题来了,要怎么伪装这个代理类,才能过JVM的安检呢。
有两种方法:
①好兄弟
实现和目标类相同的接口,我也实现和你一样的接口,反正上层都是接口级别的调用,这样我就伪装成了和目标类一样的类,一个爹的儿子,就是好兄弟,JVM一看,哦是海尔兄弟啊,就放你过去了,也就逃过了类型检查,到java运行期的时候,利用多态的后期绑定(所以spring采用运行时),伪装类(代理类)就变成了接口的真正实现,而他里面包裹了真实的那个目标类,最后实现具体功能的还是目标类,只不过伪装类在之前干了点事情(写日志,安全检查,事物等)。
这就好比张三想要拜访罗翔老师,去罗祥老师家的时候,罗翔老师的弟弟罗志祥就趴在阳台上问张三,
你是哪个?
张三分不清罗翔和罗志祥,回答说
我是张三。
然后罗志祥就在本子上记着,张三来活了。
然后张三进了屋子和罗志祥老师疯狂交流,想要聘为律师,罗志祥频频点头,搞的跟真的一样,答应了张三的请求,然后把张三愉悦送走。
回过头来罗志祥发现,坏啦爷不是律师,得让我哥来,然后罗志祥就跟罗翔老师说,今天张三来了想让你当律师。罗翔老师大喜,好啊,这个我拿手。
第二天就把问题解决了。问题解决后,罗志祥在本子上记下:张三的问题解决了。
这波啊,叫做JDK代理模式。
用代码演示一遍
代理接口,等会代理类和目标类都会实现这个接口
public interface UserDao {
void save();
}
目标对象:罗老师的工作
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("他抢我的钱我打他的人,合适吗?合适的不得了");
}
}
代理对象:罗志祥的工作
public class TransactionHandler implements InvocationHandler {
//需要代理的目标对象
//这里设计为可以为任意对象添加事务控制, 所以将目标对象声明为Object
private Object target;
//构造TransactionHandler时传入目标对象
public TransactionHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用目标方法前的处理
System.out.println("张三来案子了");
//调用目标对象的方法
Object result = method.invoke(target, args);
//调用目标方法后的处理
System.out.println("张三结案了");
//放回方法调用结果
return result;
}
}
张三(测试类)
public class Main {
public static void main(String[] args) {
//新建目标对象
Object target = new UserDaoImpl();
//创建事务处理器
TransactionHandler handler = new TransactionHandler(target);
//生成代理类并使用接口对其进行引用
UserDao userDao = (UserDao)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler();
//针对接口进行方法调用
userDao.save();
}
}
②好儿子
用于没有实现接口,好兄弟行不通了。
这时候直接创造子类,当不了兄弟当儿子。
生成子类调用,这次用子类来做为伪装类,当然这样也能逃过JVM的强类型检查,我继承的吗,当然查不出来了,子类重写了目标类的所有方法,当然在这些重写的方法中,不仅实现了目标类的功能,还在这些功能之前,实现了一些其他的(写日志,安全检查,事物等)。
例子:
罗志祥直接化身罗翔之子,学习律师知识,不仅记录张三的行踪,还亲自把事给人家办了。但是罗翔老师的举例子能力是final的,当了儿子也学不会。
这就是CGLIB。
代码实现
添加依赖包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
1.创建普通类
public class Users{
public void login(){}
}
2.创建创建CGLib代理器
class CgProxy implements MethodInterceptor {
public Object intercept(Object o, Method method,Object[]objects, MethodProxy methodProxy) throws Throwable {
System.out.println("输出语句1");
//参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法
//引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
Object obj= methodProxy.invokeSuper(o,objects);
System.out.println("输出语句2");
return obj;
}
}
测试
public static void main(String[] args) {
//1.创建真实对象
Users users = new Users();
//2.创建代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(users.getClass());
enhancer.setCallback(new CglibProxy());
Users o = (Users) enhancer.create();//代理对象
o.login(); }
CGLIB原理不谈,俺不会。
总结
前一种兄弟模式,spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。
后一种父子模式,spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。
相比之下,还是兄弟模式好些,他能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候,也能织入通知,应当做一种例外。