代理在我们日常开发中是一个很常见的知识点,也是我们面试中经常被问到的内容,本文带大家来学习和分析下代理的相关内容。
1.概念
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。
举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子.
其中的明星我们一般称为委托类,或者称之为被代理类;而明星的经纪人我们称之为代理类。
代理的优点:
隐藏委托类的实现。
解耦,在不改委托类的实现下添加一些额外操作。
2.分类
根据运行前委托类是否存在,我们将代理分为两类:
静态代理
动态代理
2.1静态代理
代理类在程序运行前已经存在的代理方式称之为静态代理。
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
2.1.1实例
/**
*接口
*/
public interface IUserDao{
void save();
}
/**
*接口实现
*目标对象
*/
public class UserDao implements IUserDao{
public void save(){
System.out.println("----已经保存数据!----");
}
}
/**
*代理对象,静态代理
*/
public class UserDaoProxy implements IUserDao{
//接收保存目标对象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save(){
System.out.println("开始事务...");
target.save();//执行目标对象的方法
System.out.println("提交事务...");
}
}
优缺点:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:
因为代理对象需要与被代理对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,被代理对象与代理对象都要维护。
那么有没有什么方法,可以解决静态代理的缺点呢?有,动态代理。
2.2动态代理
代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理。
Java提供了动态代理的实现方式,可以在运行时刻动态生成代理类。
这种代理方式的一大好处是可以方便对代理类的函数做统一或特殊处理,如记录所有函数执行时间、所有函数执行前添加验证判断、对某个特殊函数进行特殊操作,而不用像静态代理方式那样需要修改每个函数。
2.2.1实现
新建委托类
实现InvocationHandler,这是负责代理类和委托类的中间类必须实现的接口。
通过Proxy类实现新建代理类对象
新建委托类
public interface Operate{
public void operateMethod1();
public void operateMethod2();
public void operateMethod3();
}
public class OperateImpl implements Operate{
Override
public void operateMethod1(){
System.out.println("Invoke operateMethod1");
sleep(110);
}
Override
public void operateMethod2(){
System.out.println("Invoke operateMethod2");
sleep(120);
}
Override
public void operateMethod3(){
System.out.println("Invoke operateMethod3");
sleep(130);
}
private static void sleep(long millSeconds){
try{
Thread.sleep(millSeconds);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
实现InvocationHandler
public class TimingInvocationHandler implements InvocationHandler{
private Object target;
public TimingInvocationHandler(){}
public TimingInvocationHandler(Object target){
this.target=target;
}
Override
public Object invoke(Object proxy,Method method,Object[]args)throws Throwable{
long start=System.currentTimeMillis();
Object obj=method.invoke(target,args);
System.out.println(method.getName()+"cost time is:"+(System.currentTimeMillis()-start));
return obj;
}
}
target属性表示委托类对象。
InvocationHandler是负责连接代理类和委托类的中间类必须实现的接口。其中只有一个
public Object invoke(Object proxy,Method method,Object[]args)
函数需要去实现,参数:
proxy表示下面通过Proxy.newProxyInstance()生成的代理类对象。
method表示代理对象被调用的函数。
args表示代理对象被调用的函数的参数。
调用代理对象的每个函数实际最终都是调用了InvocationHandler的invoke函数。这里我们在invoke实现中添加了开始结束计时,其中还调用了委托类对象target的相应函数,这样便完成了统计执行时间的需求。
invoke函数中我们也可以通过对method做一些判断,从而对某些函数特殊处理。
生成代理对象
public class Main{
public static void main(String[]args){
//create proxy instance
TimingInvocationHandler timingInvocationHandler=new TimingInvocationHandler(new OperateImpl());
Operate operate=(Operate)(Proxy.newProxyInstance(Operate.class.getClassLoader(),new Class[]{Operate.class},
timingInvocationHandler));
//call method of proxy instance
operate.operateMethod1();
System.out.println();
operate.operateMethod2();
System.out.println();
operate.operateMethod3();
}
}
这里我们先将委托类对象new OperateImpl()作为TimingInvocationHandler构造函数入参创建timingInvocationHandler对象;
然后通过Proxy.newProxyInstance(…)函数新建了一个代理对象,实际代理类就是在这时候动态生成的。我们调用该代理对象的函数就会调用到timingInvocationHandler的invoke函数(是不是有点类似静态代理),而invoke函数实现中调用委托类对象new OperateImpl()相应的method(是不是有点类似静态代理)。
2.2.2 newProxyInstance
下面我们来分下Proxy.newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h)
loader表示类加载器
interfaces表示委托类的接口,生成代理类时需要实现这些接口
h是InvocationHandler实现类对象,负责连接代理类和委托类的中间类
我们可以这样理解,如上的动态代理实现实际是双层的静态代理,开发者提供了委托类B,程序动态生成了代理类A。开发者还需要提供一个实现了InvocationHandler的子类C,子类C连接代理类A和委托类B,它是代理类A的委托类,委托类B的代理类。用户直接调用代理类A的对象,A将调用转发给委托类C,委托类C再将调用转发给它的委托类B。
3.动态代理原理
3.1生成的动态代理类代码
下面是上面示例程序运行时自动生成的动态代理类代码。
public final class$Proxy0 extends Proxy
implements Operate
{
private static Method m4;
private static Method m1;
private static Method m5;
private static Method m0;
private static Method m3;
private static Method m2;
public$Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final void operateMethod1()
throws
{
try
{
h.invoke(this,m4,null);
return;
}
catch(Error|RuntimeException localError)
{
throw localError;
}
catch(Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final boolean equals(Object paramObject)
throws
{
try
{
return((Boolean)h.invoke(this,m1,new Object[]{paramObject})).booleanValue();
}
catch(Error|RuntimeException localError)
{
throw localError;
}
catch(Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void operateMethod2()
throws
{
try
{
h.invoke(this,m5,null);
return;
}
catch(Error|RuntimeException localError)
{
throw localError;
}
catch(Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return((Integer)h.invoke(this,m0,null)).intValue();
}
catch(Error|RuntimeException localError)
{
throw localError;
}
catch(Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void operateMethod3()
throws
{
try
{
h.invoke(this,m3,null);
return;
}
catch(Error|RuntimeException localError)
{
throw localError;
}
catch(Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return(String)h.invoke(this,m2,null);
}
catch(Error|RuntimeException localError)
{
throw localError;
}
catch(Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m4=Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod1",new Class[0]);
m1=Class.forName("java.lang.Object").getMethod("equals",new Class[]{Class.forName("java.lang.Object")});
m5=Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod2",new Class[0]);
m0=Class.forName("java.lang.Object").getMethod("hashCode",new Class[0]);
m3=Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod3",new Class[0]);
m2=Class.forName("java.lang.Object").getMethod("toString",new Class[0]);
return;
}
catch(NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch(ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
从中我们可以看出动态生成的代理类是以$Proxy为类名前缀,继承自Proxy,并且实现了Proxy.newProxyInstance(…)第二个参数传入的所有接口的类。
如果代理类实现的接口中存在非public接口,则其包名为该接口的包名,否则为com.sun.proxy。
其中的operateMethod1()、operateMethod2()、operateMethod3()函数都是直接交给h去处理,h在父类Proxy中定义为
protected InvocationHandler h;
即为Proxy.newProxyInstance(…)第三个参数。
所以InvocationHandler的子类C连接代理类A和委托类B,它是代理类A的委托类,委托类B的代理类。
3.2生成动态代理类原理
newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[]interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if(h==null){
throw new NullPointerException();
}
/*
*Look up or generate the designated proxy class.
*/
Class<?>cl=getProxyClass0(loader,interfaces);
/*
*Invoke its constructor with the designated invocation handler.
*/
try{
final Constructor<?>cons=cl.getConstructor(constructorParams);
return newInstance(cons,h);
}catch(NoSuchMethodException e){
throw new InternalError(e.toString());
}
}
private static Object newInstance(Constructor<?>cons,InvocationHandler h){
try{
return cons.newInstance(new Object[]{h});
}catch(IllegalAccessException|InstantiationException e){
throw new InternalError(e.toString());
}catch(InvocationTargetException e){
Throwable t=e.getCause();
if(t instanceof RuntimeException){
throw(RuntimeException)t;
}else{
throw new InternalError(t.toString());
}
}
}
从上面的代码我们可以看到,调用了getProxyClass0得到动态代理类,然后将h传入了动态代理类。
getProxyClass0
private static Class<?>getProxyClass0(ClassLoader loader,
Class<?>...interfaces){
//判断委托类的接口数量,超载直接抛出异常
if(interfaces.length>65535){
throw new IllegalArgumentException("interface limit exceeded");
}
//声明代理类对象
Class<?>proxyClass=null;
/*collect interface names to use as key for proxy class cache*/
String[]interfaceNames=new String[interfaces.length];
//for detecting duplicates
Set<Class<?>>interfaceSet=new HashSet<>();
/**
*入参interfaces检验,包含三部分
*(1)是否在入参指定的ClassLoader内
*(2)是否是Interface
*(3)interfaces中是否有重复
*/
for(int i=0;i<interfaces.length;i++){
/*
*Verify that the class loader resolves the name of this
*interface to the same Class object.
*/
String interfaceName=interfaces<i>.getName();
Class<?>interfaceClass=null;
try{
interfaceClass=Class.forName(interfaceName,false,loader);
}catch(ClassNotFoundException e){
}
if(interfaceClass!=interfaces<i>){
throw new IllegalArgumentException(
interfaces<i>+"is not visible from class loader");
}
/*
*Verify that the Class object actually represents an
*interface.
*/
if(!interfaceClass.isInterface()){
throw new IllegalArgumentException(
interfaceClass.getName()+"is not an interface");
}
/*
*Verify that this interface is not a duplicate.
*/
if(interfaceSet.contains(interfaceClass)){
throw new IllegalArgumentException(
"repeated interface:"+interfaceClass.getName());
}
interfaceSet.add(interfaceClass);
interfaceNames<i>=interfaceName;
}
/*
*Using string representations of the proxy interfaces as
*keys in the proxy class cache(instead of their Class
*objects)is sufficient because we require the proxy
*interfaces to be resolvable by name through the supplied
*class loader,and it has the advantage that using a string
*representation of a class makes for an implicit weak
*reference to the class.
*/
//以接口名对应的List作为缓存的key
List<String>key=Arrays.asList(interfaceNames);
/*
*Find or create the proxy class cache for the class loader.
*/
/*
*loaderToCache是个双层的Map
*第一层key为ClassLoader,第二层key为上面的List,value为代理类的弱引用
*/
Map<List<String>,Object>cache;
synchronized(loaderToCache){
cache=loaderToCache.get(loader);
if(cache==null){
cache=new HashMap<>();
loaderToCache.put(loader,cache);
}
/*
*This mapping will remain valid for the duration of this
*method,without further synchronization,because the mapping
*will only be removed if the class loader becomes unreachable.
*/
}
/*
*Look up the list of interfaces in the proxy class cache using
*the key.This lookup will result in one of three possible
*kinds of values:
*null,if there is currently no proxy class for the list of
*interfaces in the class loader,
*the pendingGenerationMarker object,if a proxy class for the
*list of interfaces is currently being generated,
*or a weak reference to a Class object,if a proxy class for
*the list of interfaces has already been generated.
*/
/*
*以上面的接口名对应的List为key查找代理类,如果结果为:
*(1)弱引用,表示代理类已经在缓存中
*(2)pendingGenerationMarker对象,表示代理类正在生成中,等待生成完成通知。
*(3)null表示不在缓存中且没有开始生成,添加标记到缓存中,继续生成代理类
*/
synchronized(cache){
/*
*Note that we need not worry about reaping the cache for
*entries with cleared weak references because if a proxy class
*has been garbage collected,its class loader will have been
*garbage collected as well,so the entire cache will be reaped
*from the loaderToCache map.
*/
do{
Object value=cache.get(key);
if(value instanceof Reference){
proxyClass=(Class<?>)((Reference)value).get();
}
if(proxyClass!=null){
//proxy class already generated:return it
return proxyClass;
}else if(value==pendingGenerationMarker){
//proxy class being generated:wait for it
try{
cache.wait();
}catch(InterruptedException e){
/*
*The class generation that we are waiting for should
*take a small,bounded time,so we can safely ignore
*thread interrupts here.
*/
}
continue;
}else{
/*
*No proxy class for this list of interfaces has been
*generated or is being generated,so we will go and
*generate it now.Mark it as pending generation.
*/
cache.put(key,pendingGenerationMarker);
break;
}
}while(true);
}
try{
String proxyPkg=null;//package to define proxy class in
/*
*Record the package of a non-public proxy interface so that the
*proxy class will be defined in the same package.Verify that
*all non-public proxy interfaces are in the same package.
*/
/*
*如果interfaces中存在非public的接口,则所有非public接口必须在同一包下面,后续生成的代理类也会在该包下面
*/
for(int i=0;i<interfaces.length;i++){
int flags=interfaces<i>.getModifiers();
if(!Modifier.isPublic(flags)){
String name=interfaces<i>.getName();
int n=name.lastIndexOf('.');
String pkg=((n==-1)?"":name.substring(0,n+1));
if(proxyPkg==null){
proxyPkg=pkg;
}else if(!pkg.equals(proxyPkg)){
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if(proxyPkg==null){
//if no non-public proxy interfaces,use the default package.
proxyPkg="";
}
{
//Android-changed:Generate the proxy directly instead of calling
//through to ProxyGenerator.
List<Method>methods=getMethods(interfaces);
Collections.sort(methods,ORDER_BY_SIGNATURE_AND_SUBTYPE);
validateReturnTypes(methods);
List<Class<?>[]>exceptions=deduplicateAndGetExceptions(methods);
Method[]methodsArray=methods.toArray(new Method[methods.size()]);
Class<?>[][]exceptionsArray=exceptions.toArray(new Class<?>[exceptions.size()][]);
/*
*Choose a name for the proxy class to generate.
*/
final long num;
synchronized(nextUniqueNumberLock){
num=nextUniqueNumber++;
}
String proxyName=proxyPkg+proxyClassNamePrefix+num;
//动态生成代理类的字节码
//最终调用sun.misc.ProxyGenerator.generateClassFile()得到代理类相关信息写入DataOutputStream实现
proxyClass=generateProxy(proxyName,interfaces,loader,methodsArray,
exceptionsArray);
}
//add to set of all generated proxy classes,for isProxyClass
proxyClasses.put(proxyClass,null);
}finally{
/*
*We must clean up the"pending generation"state of the proxy
*class cache entry somehow.If a proxy class was successfully
*generated,store it in the cache(with a weak reference);
*otherwise,remove the reserved entry.In all cases,notify
*all waiters on reserved entries in this cache.
*/
synchronized(cache){
if(proxyClass!=null){
cache.put(key,new WeakReference<Class<?>>(proxyClass));
}else{
cache.remove(key);
}
cache.notifyAll();
}
}
return proxyClass;
}
4.CGLib实现代理
4.1与JDK代理区别
JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final
4.2对比
JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,它有几个要求
实现InvocationHandler
使用Proxy.newProxyInstance产生代理对象
被代理的对象必须要实现接口
使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
CGLib必须依赖于CGLib的类库,Cglib原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型。针对接口编程的环境下推荐使用JDK的代理。从执行效率上看,Cglib动态代理效率较高。在Hibernate中的拦截器其实现考虑到不需要其他接口的条件Hibernate中的相关代理采用的是CGLib来执行。
4.3 CGLib实例
实例:
/**
*CGLibProxy动态代理类的实例
*
*
*/
public class CGLibProxy implements MethodInterceptor{
private Object targetObject;//CGLib需要代理的目标对象
public Object createProxyObject(Object obj){
this.targetObject=obj;
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
Object proxyObj=enhancer.create();
return proxyObj;//返回代理对象
}
public Object intercept(Object proxy,Method method,Object[]args,
MethodProxy methodProxy)throws Throwable{
Object obj=null;
if("addUser".equals(method.getName())){//过滤方法
checkPopedom();//检查权限
}
obj=method.invoke(targetObject,args);
return obj;
}
private void checkPopedom(){
System.out.println(".:检查权限checkPopedom()!");
}
}
//调用
UserManager userManager=(UserManager)new CGLibProxy()
.createProxyObject(new UserManagerImpl());
5.结束语
详细大家通过上面的学习,已经对代理有了一个更深层次的认识,代理在AOP开发中特别有用,让我们在设计和开发中把代理用起来吧。
代理IP软件一般都会有共享IP和独享IP,共享IP就是很多人使用一个IP池,别人使用过的IP可能下一个使用的就是你。独享IP就是一个人使用一个IP池,IP质量比较高。选择共享IP还是独享IP可以看自
在日常生活中,我们平时上网可能会很少用到http代理IP,但在从事互联网工作中,用到http代理IP的机会就比较多了。HTTP代理IP确实给我们的工作带来很多便利,也正因为如此,市面上的代理IP越
还记得几年前刚接触到代理IP时,那时候同事给我一个txt文本,里面几百个IP,让我尽情的使用,根本没有绑定IP授权使用这回事。不过,后来IP质量渐渐的不行了,大部分都不能使用了。现在代
怎样使用HTTP代理IP进行注册?当我们访问某网站,查看或者进行某项业务时,往往会被提示先注册登录,基本一个IP地址只能注册一个账号。对于每天有大量注册业务的网友来说,需要大量的HTTP