什么是代理?
本质上说,相当于我们平时租房子的中介,屏蔽掉了你与房主之间的联系,直接给你提供租房服务。
有的人有好多套房子,这个人就相当于是一个目标类,这个中介就是代理类,我们使用代理类就可以调用目标类的方法。代理类封装了目标类,相当于中介有房主的这些房子的信息,我们直接找中介就可以租到房子。我们通过中介,甚至还可以看其他房主的房子,也就是通过代理类调用其他类的方法,相当于实现了给目标类扩展新功能而不需要修改目标类的代码,这样一定程度上可以实现解耦。
Java中的代理有两种:
静态代理:手写代理类将目标类封装起来,缺点是需要自己写代码
动态代理:运行阶段由JVM自动生成代理类,可以直接使用
静态代理实现:
静态代理需要我们自己写,如果方法少的话感觉还行,方法多的话感觉十分崩溃,操作性太差,以下是静态代理的简单实现
首先需要包装目标类,我们先写一个接口,让目标类和代理类都实现这个接口,那么两个类的方法名就会一样,然后将目标类传入代理类中即可
//定义接口
interface animal{
void eat();
void run();
}
public class agentTest {
//目标类
public static class Cat implements animal{
//目标类实现接口中的方法
@Override
public void eat() {
System.out.println("狗吃饭");
}
@Override
public void run() {
System.out.println("狗奔跑");
}
}
//代理类
public static class Catagent implements animal{
private Cat cat;
public Catagent(Cat cat){
this.cat = cat;
}
//代理类在目标类的基础上扩展方法
@Override
public void run() {
System.out.println("代理类---跑");
cat.run();
}
@Override
public void eat() {
System.out.println("代理类---吃");
cat.eat();
}
}
public static void main(String[] args) {
Cat cat = new Cat();
Catagent catagent = new Catagent(cat);
catagent.run();
catagent.eat();
}
}
动态代理实现:
前面说了,静态代理要自己写,方法多了十分崩溃,因此我们通常使用动态代理,实现动态代理主要有两种方式,一种是基于JDK的动态代理,一种是基于CGLib的动态代理
动态代理除了目标类和代理类之外,还有中间类
基于JDK的动态代理
Java中为我们提供Proxy类的代理类,这个类中的有参构造是传递一个InvocationHandler类型的参数然后复制给属性h ,然后有一个newProxyInstance()这个方法,这个方法的参数是类加载器(通常用目标类加载器即可)、目标类的实现接口(为了让代理类和目标类的方法名保持一致)和InvocationHandler类型的参数(这个参数自己实现)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//定义接口
interface animal{
void eat();
void run();
}
public class agentTest {
//目标类
public static class Cat implements animal{
//目标类实现接口中的方法
@Override
public void eat() {
System.out.println("狗吃饭");
}
@Override
public void run() {
System.out.println("狗奔跑");
}
}
public static void main(String[] args) {
//代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
animal target = new Cat();
animal proxycat = (animal) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("现在是动态代理");
method.invoke(target,args);
return null;
}
});
proxycat.run();
proxycat.eat();
}
}
代理的作用?
主要是可以实现解耦,可以避免我们直接与目标类接触,有利于目标类的扩展。很多类中都使用了代理,用户一般不会感知到。就好像一般取餐厅吃饭都会告诉服务员不要辣椒,而不会直接去找厨师说。
基于JDK的动态代理的缺点?
基于JDK的动态代理要目标类必须实现一个或几个接口,那么如果目标类没有实现接口咋办,就。。有点尴尬。所以就有了基于CGLib的动态代理,可以弥补基于JDK的动态代理目标类必须要实现一个接口的缺陷。
基于CGLib的动态代理实现:
CGLib动态代理的原理是,代理类会继承目标类,成为目标类的子类。每次去调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑。
来写个栗子。
首先,建一个maven项目,导入CGLib依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
先写一个目标类:
//目标类
public class Dog {
public void eat(){
System.out.println("狗-----eat");
}
public void run(){
System.out.println("狗-----run");
}
}
再让代理类实现方法拦截器接口,实现代理的类:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//实现代理的类
//这个类要实现方法拦截器接口MethodInterceptor
//此接口中只有一个方法intercept,这个方法有四个参数,分别是
// 1 实现这个接口类的对象 2 被拦截的方法 3 被拦截方法的参数 4 要触发父类的方法对象
public class dogProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
// @Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("事务开始----");
methodProxy.invokeSuper(o,objects);
System.out.println("事务结束-----");
return null;
}
}
写个主函数测试一下:
//本质上,基于CGLib的动态代理的核心是enhancer类,这个类根据传递进去的参数来生成新的类的对象
public class cglibTest {
public static void main(String[] args) {
dogProxy cglib = new dogProxy();
//getInstance()方法的作用是 1 设置enhancer对象的父类 2 设置enhancer的回调对象 3 创建代理对象
Dog dog = (Dog)cglib.getInstance(new Dog());
System.out.println(dog.getClass().getName());
System.out.println(dog.getClass().getSuperclass().getName());
dog.eat();
dog.run();
}
}
运行结果:
原理:当我们调用方法时,会先在代理类中判断是否实现了方法拦截器接口,如果没实现就直接调用目标类的方法,如果实现了,就会被方法拦截器拦截,在方法拦截器里面会对类的所有方法建立索引,相当于将每个方法的引用保存在数组中,这样我们就可以根据索引直接调用方法,而不是用反射。我们要添加的逻辑就添加在方法拦截器里面。
基于CGLib的动态代理缺点:
CGLib原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明称final类型