Spring AOP预热——代理模式
SpringAOP是什么,看到AOP,大家应该会想到OOP,OOP(Object Oriented Programming)面向对象编程,AOP(Aspect Oriented Programming)面向切面编程。
前者OOP的设计特征:封装, 继承, 多态, 抽象. OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性
后者通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
代理模式
代理模式其实是23中设计模式之一,用官方的话来说就是——由于某些原因需要给某对象提供一个代理以控制对该对象的访问。其实很好理解,就是我要去做一件事,但使用代理之后,我自己不去做,而是找我的代理去做。代理类知识在之前类的基础上做了一层封装。
Java类中代理分为静态代理和动态代理。
动态代理又分为JDK动态代理和CGLib动态代理的方式。
其中静态代理是指代理类在编译器就存在的,而动态代理是程序运行期动态生成的。
静态代理
静态代理,在运行之前,代理类和被代理类已经确定了,静态代理实现过程如下,首先定义一个公共接口,给代理类和被代理类实现。
角色分析:
- 抽象角色:一般会使用接口或者抽象类解决
- 真是角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人
代码步骤:
1、接口
//租房
public interface Rent {
public void rent();
}
2、真实角色
//房东
public class Master implements Rent{
public void rent() {
System.out.println("房东要出租房子");
}
}
3、代理角色
public class Proxy implements Rent{
private Master master;
public Proxy() {
}
public Proxy(Master master) {
this.master = master;
}
public void rent() {
master.rent();
seeHouse();
getMoney();
agreement();
}
public void seeHouse()
{
System.out.println("中介带你看房");
}
public void getMoney()
{
System.out.println("收中介费");
}
public void agreement()
{
System.out.println("签租赁合同");
}
}
4、客户端访问代理角色
public class Client {
public static void main(String[] args) {
Master master = new Master();
//代理,中介帮房东租房子,但是代理角色会有一些附属操作
Proxy proxy = new Proxy(master);
proxy.rent();
}
}
代理模式的好处:
- 可以使真实的角色的操作更加纯粹!不用去关注一些公共的业务!
- 公共业务就交给代理角色!实现业务的分工
- 公共业务发生扩展的时候,方便集中管理。
缺点:
- 一个真实角色就会产生一个代理角色,代码会翻倍,开发效率会变低
实际开发中,使用动态代理,还可以在被代理方法的执行前后加入别的业务代码,实现权限、日志等操作。刚说了代理方法多的话就会增加成本,那如何解决呢?这样就引入了动态代理。
动态代理
上面说了动态代理有两种实现方式,一种是JDK动态代理还有一种是CGLib动态代理。并且动态代理是在程序运行期间,由JVM通过反射等机制动态的创建出代理对象的字节码。
动态代理的两类实现:基于接口的动态代理,基于类的动态代理
- 基于接口 — JDK 动态代理
- 基于类:cglib
JDK动态代理
JDK提供的动态代理需要实现InvocationHandler接口,这个接口里就一个方法。
invoke方法,这个方法是整个代理的入口
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
这里第一个参数是代理对象,proxy嘛 就是代理的意思咯,第二个参数是目标的方法,第三个参数就是目标的参数
还是上面租房的例子,我们有客户类,房东,租房的接口,还有一个代理类对吧,但这里代理和上面那个代理就有所不同了,这里的代理要实现InvocationHandler接口,
接口
Rent.java
public interface Rent {
public void rent();
}
代理类
ProxyInvocationHandler.java(实现InvocationHandler接口)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//等会用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//官方创建动态代理类实例
// Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
// new Class<?>[] { Foo.class },
// handler);
public Object getProxy(){
//生成得到代理类
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是使用反射机制实现!
seeHouse();
Object result = method.invoke(rent, args);
fare();
return result;
}
public void seeHouse(){
System.out.println("中介带看房子");
}
public void fare(){
System.out.println("收中介费");
}
}
真实角色
Host.java
//房东
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
客户端访问代理角色
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象
pih.setRent(host);
Rent proxy = (Rent) pih.getProxy();
//这里的proxy就是动态生成的,我们并没有写
proxy.rent();
}
}
可以看到使用了JDK代理类后不需要给每一个对象都实现一个代理类,通过处理器,可以对不同的对象生成代理类。
不管是静态代理还是动态代理,输出一样说明实现的业务无异。
PS:
这里记住了哦,JDK的动态代理是通过代理接口实现的,如果对象没有接口,那是必不能实现的。
通过CGLib实现
CGLIB是一个强大的高性能的代码生成包,在运行时动态生成字节码并生成新类,想要用它,就要实现MethodInterceptor接口,这个接口在cglib.proxy下
接口
public interface Rent {
public void rent();
}
真实角色
//房东
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
动态代理
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
System.out.println("开始CGLib代理");
Object o1 = proxy.invokeSuper(o, objects);
System.out.println("结束CGLib代理");
return o1;
}
}
测试类
public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Host.class);
enhancer.setCallback(new MyMethodInterceptor());
//生成代理类
Host host = (Host) enhancer.create();
host.rent();
System.out.println(" ----------------------");
}
}
cglib原理及实现机制:
https://blog.csdn.net/gyshun/article/details/81000997
JDK动态代理 VS CGLIB 对比
- 字节码创建方式:JDK动态代理通过JVM实现代理类字节码的创建,cglib通过ASM创建字节码。
- JDK动态代理强制要求目标类必须实现了某一接口,否则无法进行代理。而CGLIB则要求目标类和目标方法不能是final的,因为CGLIB通过继承的方式实现代理。
- CGLib不能对声明为final的方法进行代理,因为是通过继承父类的方式实现,如果父类是final的,那么无法继承父类。
性能对比
性能的对比,不是一个简单的答案,要区分JDK版本来区分,这里得出的答案是基于其他作者的测试结果得出的。
JDK1.6/1.7上的对比
- 类的创建速度:JDK快于CGLIB。
- 执行速度:JDK慢于CGLIB,大概慢2倍的关系。
JDK1.8上的对比
- 类的创建速度:JDK快于CGLIB。
- 执行速度:JDK快于CGLIB,经过努力,JDK1.8作了性能上的优化,速度明显比1.7提升了很多。1.8上JDK全面优于CGLIB,是不是说以后都不要用CGLIB,这还得看具体的类情况和场景,如果没有实现接口,就用CGLIB,使用的场景不同。
动态代理优点
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共也就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便了集中管理
- 一个动态代理类代理的是一个接口,代理的是一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可