目录
按照代理类的创建时期,可分为静态代理和动态代理:
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
动态:在程序运行时运用反射机制动态创建而成。在程序运行前不存在代理类。
一、代理
1、静态代理
public interface Flyable {
void fly(long ms);
}
public class Bird implements Flyable {
@Override
public void fly(long ms) {
System.out.println("bird is flying!");
try {
Thread.sleep(ms);
} catch (Exception e) {
}
}
}
public class Kite implements Flyable {
@Override
public void fly(long ms) {
System.out.println("kite is flying!");
try {
Thread.sleep(ms);
} catch (Exception e) {
}
}
}
public class StaticProxy implements Flyable {
private Flyable flyable;
public StaticProxy(Flyable flyable) {
this.flyable = flyable;
}
@Override
public void fly(long ms) {
try {
System.out.println("before invoke ");
long begin = System.currentTimeMillis();
flyable.fly(ms);
long end = System.currentTimeMillis();
System.out.println("after invoke elpased " + (end - begin));
} catch (Exception e) {
e.printStackTrace();
System.out.println("invoke failed!");
throw e;
}
}
}
public static void main(String[] args) {
StaticProxy staticProxyBird = new StaticProxy(new Bird());
staticProxyBird.fly(100);
StaticProxy staticProxyKite = new StaticProxy(new Kite());
staticProxyKite.fly(200);
}
静态代理其实就是对方法的增强,做到在不修改目标对象的前提下,拓展目标对象的功能。但静态代理有2个缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为Flyable类的访问提供了代理,但是如果还要为其他类提供代理的话,就需要我们再次添加代理类。
2、JDK动态代理
在动态代理中,Proxy代理类在编译期是不存在的,而是在程序运行时被动态生成的,因为有了反射,可以根据传入的参数,生成你想要的代理(如你想代理A就代理A,想代理B就代理B),实现原理就是在生成Proxy的时候你需要传入被代理类的所有接口(如果没有接口是另一种方式,下文会提),反射机制会根据你传入的所有接口,帮你生成一个也实现这些接口的代理类出来。之后,代理对象每调用一个方法,都会把这个请求转交给InvocationHandler来执行,而在InvocationHandler里则通过反射机制,继续转发请求给真正的目标对象,最后由目标对象来返回结果。
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强。
public class DynamicProxy implements InvocationHandler {
private Object targetObject;
public Object newProxyInstance(Object targetObject) {
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
System.out.println("before invoke ");
long begin = System.currentTimeMillis();
proxy = method.invoke(targetObject, args);
long end = System.currentTimeMillis();
System.out.println("after invoke elpased " + (end - begin));
} catch (Exception e) {
e.printStackTrace();
System.out.println("invoke failed!");
throw e;
}
return proxy;
}
}
public static void main() {
DynamicProxy dynamicProxy = new DynamicProxy();
Flyable bird = (Flyable) dynamicProxy.newProxyInstance(new Bird());
bird.fly(100);
Flyable kite = (Flyable) dynamicProxy.newProxyInstance(new Kite());
kite.fly(200);
}
调用顺序:
(1)获取动态代理对象 Flyable
(2)调用Flyable 的方法 fly ,这会转发到 DynamicProxy 的invoke方法内调用,这里的 invoke 实际上就是对转发的方法的进行的操作,invoke中会先执行增强方法,再调用 method.invoke通过反射调用被代理类的原方法。
疑问1:JDK动态代理为什么必须针对接口
由于java的单继承,动态生成的代理类已经继承了Proxy类的,目的是为了反射生成对应的方法,因此就不能再继承其他的类,所以只能靠实现被代理类的接口的形式去增强方法,故JDK的动态代理必须有接口。
疑问2:为何复写 invoke()方法
其实是因为在动态代理类的定义中,构造函数是含参的构造,参数就是我们invocationHandler 实例,而每一个被代理接口的方法都会在代理类中生成一个对应的实现方法,并在实现方法中调用调度器invocationHandler 的invoke方法,这就解释了为何执行代理类的方法会自动进入到我们自定义的invocationHandler的invoke方法中,然后在我们的invoke方法中再利用jdk反射的方式去调用真正的被代理类的业务方法,而且还可以在方法的前后去加一些我们自定义的逻辑。比如切面编程AOP等。
3、CGLIB代理
上面的静态代理和JDK代理模式都需要目标对象是一个实现了接口的目标对象,但是有的时候,目标对象可能只是一个单独的对象,并没有实现任何的接口,这个时候,我们就可以使用目标对象子类的方式实现代理,这种代理方式就是:Cglib代理,也叫做子类代理,它是在内存中构件一个子类对象,从而实现对目标对象的功能拓展。
Cglib是强大的高性能的代码生成包,它可以在运行期间拓展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception
(拦截)。
Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,不鼓励直接只使用ASM,因为它要求你必须对JVM内部结构,包括class文件的格式和指令集都很熟悉。
public class Plane {
public void fly(long ms) {
System.out.println("plane is flying!");
try {
Thread.sleep(ms);
} catch (Exception e) {
}
}
}
public class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
public Object getProxyInstance() {
//1. 实例化工具类
Enhancer en = new Enhancer();
//2. 设置父类对象
en.setSuperclass(this.target.getClass());
//3. 设置回调函数
en.setCallback(this);
//4. 创建子类,也就是代理对象
return en.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before invoke ");
long begin = System.currentTimeMillis();
//执行目标对象的方法
Object returnValue = method.invoke(target, objects);
long end = System.currentTimeMillis();
System.out.println("after invoke elpased " + (end - begin));
return returnValue;
}
}
public static void main() {
CglibProxy cglibProxy = new CglibProxy(new Plane());
Plane plane = (Plane) cglibProxy.getProxyInstance();
plane.fly(150);
}
4、代理模式和装饰器模式的区别
二者都是实现方法增强,但是侧重点不同。
装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。
二、aop与动态代理的关系
使用aop增强类的某些方法:
public interface Waiter {
// 向客人打招呼
void greetTo(String clientName);
// 服务
void serveTo(String clientName);
}
public class NaiveWaiter implements Waiter {
public void greetTo(String clientName) {
System.out.println("NaiveWaiter:greet to " + clientName + "...");
}
public void serveTo(String clientName) {
System.out.println("NaiveWaiter:serving " + clientName + "...");
}
}
public interface Seller {
// 卖东西
int sell(String goods, String clientName);
}
public class SmartSeller implements Seller {
// 卖东西
public int sell(String goods, String clientName) {
System.out.println("SmartSeller: sell " + goods + " to " + clientName + "...");
return 100;
}
}
如上示例代码,有一个服务员的接口,还有一个售货员的接口,现在想做的就是:想这个服务员可以充当售货员的角色,可以卖东西。
引入切面
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
@Aspect
public class EnableSellerAspect {
@DeclareParents(value = "com.example.demo.NaiveWaiter", // 切点(目标类)
defaultImpl = SmartSeller.class) // 增强类
public Seller seller; // 增强类接口
}
测试方法
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter");
// 调用服务员原有的方法
waiter.greetTo("Java3y");
waiter.serveTo("Java3y");
// 通过引介/引入切面已经将waiter服务员实现了Seller接口,所以可以强制转换
Seller seller = (Seller) waiter;
seller.sell("水军", "Java3y");
}
}
当引入接口方法被调用时,代理对象会把此调用委托给实现了新接口的某个其他对象。实际上,一个Bean的实现被拆分到多个类中。如引用增强方法,则委托给实现了新接口的某个其他对象。如引用非增强方法,则调用接口本身。
参考: