什么是代理
代理就是代理方从被代理方获取某些权限,从而为被代理方服务,例如:
我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。我们把微商代理和厂家进一步抽象,前者可抽象为代理类,后者可抽象为委托类(被代理类)。通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:
- 优点一:可以隐藏委托类的实现;
- 优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。
有时候我们需要对一个类添加相应的功能或对某些结果进行修改,但是直接修改源码会对维护等提高困难,
所以我们才会有代理的思想,就是说,我不修改你源码了,我让一个“中间类”来办这件事,来调用你的方法,然后我在中间类中添加或修改相应的功能,我想怎么实现,想怎么添加都行。这样极大的保护了源码,而且下次你功能优点变化,我修改修改代理类,实在不行,我重新定义一个代理类,这样的维护成本比你直接修改源码要低?这就是AOP(面向切面编程)。
在java中代理又分为静态代理和动态代理,一般而言我们都是使用动态代理,静态代理的局限性太大,不过也可以让我们对代理的概念有更好的了解,下面我们来了解一下java中的代理。
静态代理
静态代理实例
Car接口
package prox;
public interface Car {
public void run();
}
被代理类SUVCar
package prox;
public class SUVCar implements Car {
@Override
public void run() {
System.out.println("SUV is running");
}
}
代理类CarProx
package prox;
public class CarProx extends SUVCar{
private SUVCar suvcar;
public CarProx(){
super();
suvcar=new SUVCar();
}
@Override
public void run() {
befor();
super.run();
after();
}
private void after() {
System.out.println("after running");
}
private void befor() {
System.out.println("befor running");
}
}
测试用例
/**
* 静态代理,代理类继续被代理类,在初始化时生成被代理类对象
* 实现被代理对象的方法,客户端调用的代理的方法,代理对象调用被代理对象同样的方法
* 在调用的前后可以对被代理对象进行操作
*/
@Test
public void prox(){
Car car = new CarProx();
car.run();
}
静态代理一般结构:
1、通用接口
2、被代理对象实现接口
3、代理对象继承被代理对象或者接口
4、客户端调用代理对象
通过以上代码,我们可以看到我们在代理类初始化时生成被代理对象的实例对象,重写和被代理对象相同的方法,从而可以将代理对象当被代理对象使用,在调用代理对象的方法run()时,调用被代理对象的run()方法,实现代理功能,并且在代理的方法前后调用了after()和befor()方法,实现了对被代理对象功能的拓展。
静态代理的缺点
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。
举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,修改,以及查询都需要添加上打印日志的功能)
即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。
动态代理
根据如上的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理
在上面的示例中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象
实现动态代理有两种方法,一种是JDK动态代理,一种是CGLIB动态代理。下面我们来看看这两种动态大理的实现方法。
一、JDK动态代理
在Java中要想实现JDK动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持
java.lang.reflect.InvocationHandler接口的定义如下:
//Object proxy:被代理的对象
//Method method:要调用的方法
//Object[] args:方法调用时所需要参数
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
java.lang.reflect.Proxy类的定义如下:
//CLassLoader loader:类的加载器
//Class<?> interfaces:得到全部的接口
//InvocationHandler h:得到InvocationHandler接口的子类的实例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
1.1、普通的代理模式
代理对象
package prox;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,
实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类
*/
public class LogSuvCar implements InvocationHandler{
private Object targetObject;
//绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
public Object newProxyInstance(Object targetObject){
//该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
//第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
//第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
//第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
//根据传入的目标返回一个代理对象
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), this);
}
@Override
/*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("start------------>>");
if(args!=null){
for(int i=0;i<args.length;i++){
System.out.println(args[i]);
}
}
Object ret=null;
try{
/*原对象方法调用前处理日志信息*/
System.out.println("satrt-->>");
//调用目标方法
ret=method.invoke(targetObject, args);
/*原对象方法调用后处理日志信息*/
System.out.println("success-->>");
}catch(Exception e){
e.printStackTrace();
System.out.println("error-->>");
throw e;
}
return ret;
}
}
测试用例
LogSuvCar logHandler=new LogSuvCar();
Car suvcar=(Car)logHandler.newProxyInstance(new SUVCar());
suvcar.run();
代理实现类LogSuvCar实现InvocationHandler接口,重写invoke方法,在invoke方法中进行代理操作
LogSuvCar的newProxyInstance传入被代理对象赋值从而交给invoke方法代理,并且通过Proxy.newProxyInstance方法根据被代理对象的加载器,接口和代理类生成动态代理对象
调用动态代理对象的方法即可进行代理
1.2、匿名代理模式
@Test
public void movePorxy2(){
SUVCar suvcar=new SUVCar();
Car car=(Car)Proxy.newProxyInstance(suvcar.getClass().getClassLoader(), suvcar.getClass().getInterfaces()
, new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("start------------>>");
if(args!=null){
for(int i=0;i<args.length;i++){
System.out.println(args[i]);
}
}
Object ret=null;
try{
/*原对象方法调用前处理日志信息*/
System.out.println("satrt-->>");
//调用目标方法
ret=method.invoke(suvcar, args);
/*原对象方法调用后处理日志信息*/
System.out.println("success-->>");
}catch(Exception e){
e.printStackTrace();
System.out.println("error-->>");
throw e;
}
return ret;
}
});
car.run();
}
1、首先生成被代理对象SUVCar
2、通过Proxy.newProxyInstance方法生成动态代理对象
3、newProxyInstance方法的第单个参数通过匿名方法生成实现接口InvocationHandle的对象
4、在匿名类中实现invoke方法实现代理
二、CGLIB动态代理
Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。
运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。
2.1、Cglib的拦截器和过滤器
拦截器:实现MethodInterceptor接口的类,在intercept方法中实现对代理目标类的方法拦截。但同时Cglib为简化和提高性能提供了一些专门的回调类型如FixedValue(可以在实现的方法loadObject中指定返回固定值,而不调用目标类函数)、NoOp(把对回调方法的调用直接委派到这个方法的父类,即不进行拦截)
过滤器:实现CallbackFilter接口的类,通过accept方法返回一个下标值,用于指定调用哪个拦截器进行拦截处理
2.2、cglib实例
使用cglib需要引入两个jar包:cglib.jar,asm.jar,在marven中添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
1、被代理类
/**
*
*/
package prox.cglib;
/**
* @author Administrator
*cglib被代理类
*/
public class TDao {
/**
*
*/
public void add(){
System.out.println("添加动作被执行");
}
}
2、代理对象生成器
/**
*
*/
package prox.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* @author Administrator
*cglib代理类
*/
public class CglibProxyFactory {
private Object obj;
public CglibProxyFactory(Object obj) {
this.obj = obj;
}
public Object getProxyFactory(){
//Enhancer类是cglib中的一个字节码增强器,它可以方便的为你所要处理的类进行扩展
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());//将目标对象所在的类作为Enhaner类的父类
enhancer.setCallback(new MethodInterceptor() {
//通过实现MethodInterceptor实现方法回调
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("事务开启...");
method.invoke(obj, args);
System.out.println("事务结束...");
return proxy;
}
});
return enhancer.create();//生成目标对象并返回
}
}
3、测试类
/**
*
*/
package prox.cglib;
/**
* @author Administrator
*
*/
public class TestCglibProxy {
public static void main(String[] args) {
TDao userDao = new TDao();
TDao userDaoProxy = (TDao)new CglibProxyFactory(userDao).getProxyFactory();
userDaoProxy.add();
System.out.println("目标对象类型:"+userDao.getClass());
System.out.println("代理对象类型:"+userDaoProxy.getClass());
}
}
结果
事务开启...
添加动作被执行
事务结束...
目标对象类型:class prox.cglib.TDao
代理对象类型:class prox.cglib.TDao$$EnhancerByCGLIB$$f50b341e
JDK代理与cglib代理的区别
1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。
JDK代理与cglib代理的效率
1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,
在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,
因为CGLib原理是动态生成被代理类的子类。
2)在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,
只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,
总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。
spring对动态代理的选择
Spring如何选择用JDK还是CGLiB?
1)当Bean实现接口时,Spring就会用JDK的动态代理。
2)当Bean没有实现接口时,Spring使用CGlib是实现。
3)可以强制使用CGlib(在spring配置中加入