一.概念
代理是什么呢?举个例子,一个公司是卖摄像头的,但公司不直接跟用户打交道,而是通过代理商跟用户打交道。如果:公司接口中有一个卖产品的方法,那么公司需要实现这个方法,而代理商也必须实现这个方法。如果公司卖多少钱,代理商也卖多少钱,那么代理商就赚不了钱。所以代理商在调用公司的卖方法后,加上自己的利润然后再把产品卖给客户。而客户部直接跟公司打交道,或者客户根本不知道公司的存在,然而客户最终却买到了产品。
专业点说:代理模式是对象的结构型模式,代码模式给某一个对象提供代理,并由代理对象控制原对象(目标对象,被代理对象)的引用。简单点说,就是通过一个工厂生成一个类的代理对象,当客户端使用的时候不直接使用目标对象,而是直接使用代理对象。
二.jdk的静态代理
Jdk的静态代理要求,目标对象和代理对象都要实现相同的接口。然后提供给客户端使用。这个代理对客户端是可见的,其结果图如下:
下面给出一个例子:
首先建立1个接口:UserService.java定义如下方法:
public interface UserService {
public void addUser(String userId,String userName);
public void delUser(String userId);
public void modfiyUser(String userId,String userName);
public String findUser(String userId);
}
然后实现这个接口的目标对象:UserServiceImpl.java
public class UserServiceImpl implements UserService {
@Override
public void addUser(String userId, String userName) {
System.out.println("UserServiceImpl addUser userId->>"+userId);
}
@Override
public void delUser(String userId) {
System.out.println("UserServiceImpl delUser userId->>"+userId);
}
@Override
public void modfiyUser(String userId, String userName) {
System.out.println("UserServiceImpl modfiyUser userId->>"+userId);
}
@Override
public String findUser(String userId) {
System.out.println("UserServiceImpl findUser userId->>"+userId);
return "张山";
}
}
为目标对象创建代理对象:UserServiceImplProxy.java代理对象持有目标对象的引用。
public class UserServiceImplProxy implements UserService {
private UserService userService;
public UserServiceImplProxy(UserService userService){
this.userService = userService;
}
@Override
public void addUser(String userId, String userName) {
try {
System.out.println("开始执行:addUser");
userService.addUser(userId, userName);
System.out.println("addUser执行成功。");
} catch (Exception e) {
System.out.println("addUser执行失败。");
}
}
@Override
public void delUser(String userId) {
}
@Override
public void modfiyUser(String userId, String userName) {
}
@Override
public String findUser(String userId) {
return null;
}
}
最后调用代理对象完成功能:Client.java
public class Client {
public static void main(String[] args) {
UserService userService = new UserServiceImplProxy(new UserServiceImpl());
userService.addUser("001", "centre");
}
}
执行结果:
开始执行:addUser
UserServiceImpl addUser userId->>001
addUser执行成功。
静态代理要为每个目标类创建一个代理类,当需要代理的对象太多,那么代理类也变得很多。同时代理类违背了可重复代理只写一次的原则。我们接下来看看动态代理。
动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等,动态代理分为jdk动态代理和cglib动态代理
三.jdk动态代理
jdk给我们提供了动态代理。其原理图如下:
Jdk的动态要求目标对象必须实现接口,因为它创建代理对象的时候是根据接口创建的。如果不实现接口,jdk无法给目标对象创建代理对象。被代理对象可以可以实现多个接口,创建代理时指定创建某个接口的代理对象就可以调用该接口定义的方法了。
首先定义2个接口:Service接口和UserService接口(上面的接口)
Service.java
public interface Service {
public void sayHello(String name);
public int addOperter(int num,int num2);
}
然后定义实现这2个接口的目标对象:UserServiceImpl.java
public class UserServiceImpl implements UserService, Service{
@Override
public void addUser(String userId, String userName) {
System.out.println("UserServiceImpl addUser userId->>" + userId);
}
@Override
public void delUser(String userId) {
System.out.println("UserServiceImpl delUser userId->>" + userId);
}
@Override
public void modfiyUser(String userId, String userName) {
System.out.println("UserServiceImpl modfiyUser userId->>" + userId);
}
@Override
public String findUser(String userId) {
System.out.println("UserServiceImpl findUser userId->>" + userId);
return "张山";
}
@Override
public void sayHello(String name) {
System.out.println("你好:" + name);
}
@Override
public int addOperter(int num, int num2) {
return num + num2;
}
}
提供一个生成代理对象的的类:LogHandler.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LogHandler implements InvocationHandler {
private Object targertObject;
public Object newInstance(Object targertObject) {
this.targertObject = targertObject;
Class targertClass = targertObject.getClass();
return Proxy.newProxyInstance(targertClass.getClassLoader(), targertClass.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法" + method.getName());
Object ret = null;
try {
ret = method.invoke(targertObject, args);
System.out.print("成功调用方法:" + method.getName() + ";参数为:");
for (int i = 0; i < args.length; i++) {
System.out.print(args[i] + ",");
}
System.out.println();
} catch (Exception e) {
e.printStackTrace();
System.out.print("调用方法:" + method.getName() + "失败;参数为:");
for (int i = 0; i < args.length; i++) {
System.out.print(args[i] + ",");
}
System.out.println();
}
return ret;
}
}
编写一个客户端类来使用工厂类生成目标类的代理:Client.java
public class Client {
public static void main(String[] args) {
Service service = (Service)new LogHandler().newInstance(new UserServiceImpl());
UserService userService = (UserService)new LogHandler().newInstance(new UserServiceImpl());
userService.addUser("001", "centre");
String name = userService.findUser("002");
System.out.println(name);
int num = service.addOperter(12, 23);
System.out.println(num);
service.sayHello("centre");
}
}
执行结果:
调用方法addUser
UserServiceImpl addUser userId->>001
成功调用方法:addUser;参数为:001,centre,
调用方法findUser
UserServiceImpl findUser userId->>002
成功调用方法:findUser;参数为:002,
查询结果张山
调用方法addOperter
成功调用方法:addOperter;参数为:12,23,
计算结果:35
调用方法sayHello
你好:centre
成功调用方法:sayHello;参数为:centre,
四.cglib 动态代理
jdk给目标类提供动态要求目标类必须实现接口,当一个目标类不实现接口时,jdk是无法为其提供动态代理的。cglib 却能给这样的类提供动态代理。Spring在给某个类提供动态代理时会自动在jdk动态代理和cglib动态代理中动态的选择。
使用cglib为目标类提供动态代理:需要导入cglib.jar和asm.jar
如果出现asm中的类无法找到的异常,在java工程中是真的缺少asm.jar,而在web工程中很可能是asm.jar和spring提供的org.springframework.asm-3.0.4.RELEASE.jar包冲突。
首先编写一个目标类:UserServiceImpl.java(上面的类),
然后为其创建一个代理工厂,用于生成目标类的代理对象:CglibProxy.java
注意:如果一个类继承了某个类,在子类中没有一个方法,用cglib生成该子类的动态代理类中将没有一个方法。
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("调用的方法是:" + method.getName());
Object ret = null;
try {
ret = proxy.invokeSuper(obj, args);
System.out.print("成功调用方法:" + method.getName() + ";参数为:");
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
}
} catch (Exception e) {
e.printStackTrace();
System.out.print("调用方法:" + method.getName() + "失败;参数为:");
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
}
}
return ret;
}
}
编写一个客户端使用代理工厂生成代理对象:CglibClient.java
import net.sf.cglib.proxy.Enhancer;
public class CglibClient {
public static void main(String[] args) {
cglibUse1();
}
public static void cglibUse1() {
Enhancer enhancer = new Enhancer();
// 设置被代理的类(目标类)
enhancer.setSuperclass(UserServiceImpl.class);
//使用回调
enhancer.setCallback(new CglibProxy());
// 创造 代理 (动态扩展了UserServiceImpl类)
UserServiceImpl my = (UserServiceImpl) enhancer.create();
//my.addUser("001", "centre");
int ret = my.addOperter(15, 22);
System.out.println("返回的结果是:" + ret);
}
}
五. jdk动态和cglib动态代理比较
Spring aop 源码中用到了俩种动态代理来实现拦截切入的功能:jdk动态代理和cglib动态代理 。
jdk动态代理由java内部反射机制来生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGLib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
Jdk动态代理要求被代理的类要实现接口,而CGLib不需要,CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)
总体来说:反射机制在类生成过程比较高效,asm 在生成类后执行比较高效。
那么它们的效率是怎么样的呢?可以做如下测试:
我们用jdk和cglib动态代理分别为同一个代理创建10000个代理对象。
代码1:
public static void testFlexiable() {
UserService test = new UserServiceImpl();
long nums = 1000;
Date start = new Date();
for (int i = 0; i < nums; i++) {
UserService userService = (UserService) new LogHandler().newInstance(test);
}
Date end = new Date();
System.out.println("创建" + nums + "个代理代理对象用时:" + (end.getTime() - start.getTime()) + "毫秒。");
}
执行结果:
创建1000个代理代理对象用时:32毫秒。
创建1000个代理代理对象用时:31毫秒。
创建1000个代理代理对象用时:31毫秒。
创建1000个代理代理对象用时:31毫秒。
创建10000个代理代理对象用时:94毫秒。
创建10000个代理代理对象用时:78毫秒。
创建10000个代理代理对象用时:78毫秒。
创建10000个代理代理对象用时:78毫秒。
代码2:
public static void testFlexiable(){
Enhancer enhancer = new Enhancer();
// 设置被代理的类(目标类)
enhancer.setSuperclass(UserServiceImpl.class);
//使用回调
enhancer.setCallback(new CglibProxy());
long nums = 1000;
Date start = new Date();
for (int i = 0; i < nums; i++) {
UserServiceImpl my = (UserServiceImpl) enhancer.create();
}
Date end = new Date();
System.out.println("创建"+nums+"个代理代理对象用时:"+(end.getTime()-start.getTime())+"毫秒。");
}
执行结果:
创建1000个代理代理对象用时:47毫秒。
创建1000个代理代理对象用时:62毫秒。
创建1000个代理代理对象用时:62毫秒。
创建1000个代理代理对象用时:47毫秒。
创建1000个代理代理对象用时:47毫秒。
创建1000个代理代理对象用时:47毫秒。
创建10000个代理对象会抛异常,cglib运行速度明显比jdk动态代理慢,由于是通过类创建的子类,比jdk通过接口创建代理更耗内存。因此在ssh框架中,spring通过为类提供代理采用jdk比cglib应该要好一些吧。
参考:
https://www.cnblogs.com/duanxz/p/4410405.html
陛下...看完奏折,点个赞再走吧!
推荐阅读
技术:奇怪的Java题:为什么128 == 128返回为false,而127 == 127会返回为true?
技术:Java面试必备技能
技术:Linux 命令行快捷键
技术:IaaS,PaaS和SaaS,QPS,RT和TPS,PV,UV和IP到底是什么意思?
工具:通过技术手段 “干掉” 视频APP里讨厌的广告之(腾讯视频)
博主12年java开发经验,现从事智能语音工作的研发,关注微信公众号与博主进行技术交流!更过干货资源等你来拿!