什么是代理?
生活中的代理是很常见的,比如代购、律师、中介等,他们都有一个共性就是帮助被代理人处理一些前前后后的事情。而被代理人只需要专注做自己要做的那部分事情就可以了。
Java中的代理也是类似的,代理模式可以实现帮助被代理者完成一些前期的准备工作和后期的善后工作,但是核心的业务逻辑仍然是由被代理者完成。
jdk静态代理
在了解什么是动态代理时,先了解什么是静态代理,用中介和房主这个关系来理解一下。
package ex.hql;
//定义一个接口Rent,有租房子这个方法
public interface Rent {
public void RentHouse();
}
我们定义一个接口也就是一个规范,房主和中介都是需要实现这个接口,因为他们最终的目的都是把房子出租出去。
package ex.hql;
//房主实现这个接口
public class Host implements Rent {
public void RentHouse() {
System.out.println("我要出租房子");
}
}
package ex.hql;
public class Agency implements Rent {
//代理类的对象
private Host host;
public Agency(Host host) {
this.host = host;
}
public void RentHouse() {
this.seeHouse();
host.RentHouse();
this.decidemoney();
}
public void seeHouse(){
System.out.println("中介带人看房子");
}
public void decidemoney(){
System.out.println("中介开始推销,谈价格");
}
}
package ex.hql;
public class Test {
public static void main(String[] args) {
Host host=new Host();
Agency agency=new Agency(host);
agency.RentHouse();
}
}
可以看到主要的逻辑代码出租房子还是不变的,中介只是起到了方法的增强作用,在出租房子前先带人看一波,再用他的推销手段将房子出租出去,房主只需要关注自己任务出租房子,其他的都交给中介。这样的代理是不错,但是当我们有很多接口,我们是不是需要为这些接口都创建一个代理类,这样岂不是又变麻烦了,那有没有什么办法可以解决,那就是动态代理。
jdk动态代理
在实现动态代理的时候我们需要用到两个类
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
还有两个方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
这个方法先为指定的接口创建代理类,然后会生成代理类的一个实例,我们原本接口的class是没有构造函数的,这个方法相当于复制了一份我们原来接口的class,但是不同的是,这份class信息里面有构造函数
参数:
loader:指定类加载器
Class<?>[] interfaces:指定需要的接口列表
InvocationHandler h:一个InvocationHandler 对象
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
参数:
proxy 调用这个方法的代理实例
method 要调用的方法
args 方法调用时所需要的参数
可以看到我们代理对象的方法都会经过invoke()方法这个中转站。
还是用房主和中介的关系来写动态代理的代码。
package ex.xy;
//还是定义一个接口
public interface Rent {
public void RentHouse();
}
package ex.xy;
//房主实现这个接口
public class Host implements Rent {
public void RentHouse() {
System.out.println("房主要出租房子");
}
}
接下来的代码就不一样了
package ex.xy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Proxytest implements InvocationHandler {
private Object object;
public Proxytest(Object o){
this.object=o;
}
public Object getproxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),this.object.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.seeHouse();
Object result= method.invoke(object,args);
this.decidemoney();
return result;
}
public void seeHouse(){
System.out.println("中介带人看房子");
}
public void decidemoney(){
System.out.println("中介开始推销,谈价格");
}
}
package ex.xy;
public class Test {
public static void main(String[] args) {
Host host=new Host();
Proxytest proxytest=new Proxytest(host);
Rent rent=(Rent)proxytest.getproxy();
rent.RentHouse();
}
}
也是一模一样的输出,虽然乍一看我们的代码好像还变多了,但是不要慌,我们的通用性大大的上升了,不信我们换个数据库的增删查改试试,要求我们在进入方法前要提示进入,结束方法时候要提示结束方法。
package ex.ex.sql;
//定义一个Crud的接口
public interface Crud {
public void insert();
public void update();
public void delete();
public void select();
}
package ex.ex.sql;
//定义类实现了Crud接口
public class Crudservice implements Crud {
public void insert() {
System.out.println("我是insert");
}
public void update() {
System.out.println("我是update");
}
public void delete() {
System.out.println("我是delete");
}
public void select() {
System.out.println("我是select");
}
}
接下来的代码注意看
package ex.ex.sql;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Proxytest implements InvocationHandler {
private Object object;
public Proxytest(Object o){
this.object=o;
}
public Object getproxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),this.object.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我进入了"+method.getName()+"方法");
Object result= method.invoke(object,args);
System.out.println("结束了"+method.getName()+"方法");
return result;
}
}
package ex.ex.sql;
public class Test {
public static void main(String[] args) {
Crudservice crudservice=new Crudservice();
Proxytest proxytest=new Proxytest(crudservice);
Crud crud1=(Crud)proxytest.getproxy();
crud1.insert();
crud1.delete();
crud1.update();
crud1.select();
}
}
可以看出我们并没有创建一个新的代理类,只是在原有的通用代理类中小小的改动了一下,这样一劳永逸的方法谁见了不爱。
但是jdk中的Proxy只能为接口生成代理类,如果你想给某个类创建代理类,那么Proxy是无能为力的,此时需要我们用到下面要说的cglib了。
cglib代理
cglib是一个强大、高性能的字节码生成库,它用于在运行时扩展Java类和实现接口;本质上它是通过动态的生成一个子类去覆盖所要代理的类(非final修饰的类和方法)。Enhancer可能是CGLIB中最常用的一个类,和jdk中的Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对final类进行代理操作。cglib底层通过修改字节码的方式为需要代理的类创建了一个子类。
在用之前需要导入相应的依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
cglib包下有个接口 MethodInterceptor会拦截所有的方法
public interface MethodInterceptor extends Callback {
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
参数:
var1 代理对象
var2 被代理的类的方法
var3 调用方法传递的参数
var4 方法代理对象
代码如下:
package ex.cglib;
//创建一个类有两个方法
public class Crudservice {
public void service1(){
System.out.println("我是service1方法");
}
public void service2(){
System.out.println("我是service2方法");
}
}
package ex.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Cglibtest implements MethodInterceptor {
private static Enhancer enhancer=new Enhancer();// //使用Enhancer来给某个类创建代理类
private Class aClass;
public Cglibtest(Class aClass){
this.aClass=aClass;
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用方法"+method.getName());
Object result= methodProxy.invokeSuper(o,objects);//调用被代理类的方法
return result;
}
public Object createObject(){
enhancer.setSuperclass(this.aClass);//通过setSuperclass来设置父类型,即需要给哪个类创建代理类
/*设置回调,需实现org.springframework.cglib.proxy.Callback接口,
此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接口,实现了Callback接口,
当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的intercept方法处理*/
enhancer.setCallback(this);
return enhancer.create();//调用enhancer.create方法获取代理对象
}
}
package ex.cglib;
public class Test {
public static void main(String[] args) {
Cglibtest cglibtest=new Cglibtest(Crudservice.class);
Crudservice crudservice=(Crudservice)cglibtest.createObject();
crudservice.service1();
crudservice.service2();
}
}
cglib包下还有个接口FixedValue会拦截所有的方法并返回固定的值
public interface FixedValue extends Callback {
Object loadObject() throws Exception;
}
代码如下:
package ex.cglib;
//创建一个类有两个方法,都有不同的返回值
public class Crudservice {
public String service1(){
System.out.println("我是service1方法");
return "Hello XiaoYi";
}
public String service2(){
System.out.println("我是service2方法");
return "Hi XiaoYi";
}
}
package ex.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.FixedValue;
public class Cglibtest2 implements FixedValue {
private static Enhancer enhancer=new Enhancer();// //使用Enhancer来给某个类创建代理类
private Class aClass;
public Cglibtest2(Class aClass){
this.aClass=aClass;
}
public Object loadObject() throws Exception {
return "LOVE";
}
public Object createObject(){
enhancer.setSuperclass(this.aClass);//通过setSuperclass来设置父类型,即需要给哪个类创建代理类
//设置回调,本次设置的是FixedValue,会返回固定的值
enhancer.setCallback(this);
return enhancer.create();//调用enhancer.create方法获取代理对象
}
}
package ex.cglib;
public class Test {
public static void main(String[] args) {
Cglibtest2 cglibtest2=new Cglibtest2(Crudservice.class);
Crudservice crudservice=(Crudservice)cglibtest2.createObject();
System.out.println(crudservice.service1());
System.out.println(crudservice.service2());
}
}
还有个接口CallbackFilter
public interface CallbackFilter {
int accept(Method var1);
boolean equals(Object var1);
}
通过这个接口可以配置不同的方法使用不同的拦截器,这里就不深入了。
总结
Java动态代理只能够对接口进行代理,不能对普通的类进行代理,cglib能够代理普通类;
Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;cglib使用asm框架直接对字节码进行操作,在类的执行过程中比较高效