java学习笔记:动态代理Proxy
动态代理Proxy
----
装饰者模式、动态代理:解决一类问题,即:不修改系统源代码的基础上,需要对源代码的某些方法进行增强。
一、装饰者模式
1.谷歌汽车案例
*_JAVA设计了一个标准接口: jdk
public interface ICar{
public void start();
public void run();
public void stop();
}
*_Google在研发汽车的同时实现了业界标准ICar 谷歌开发人员写驱动包
public class GoogleCar implement ICar{
@Override
public void start() {
System.out.println("谷歌的汽车启动了");
}
@Override
public void run() {
System.out.println("谷歌的汽车运行中");
}
@Override
public void stop() {
System.out.println("谷歌的汽车停止中");
}
}
当我们要为谷歌汽车的启动方法增强一些功能时,可以考虑采用以下方案:
2.考虑用继承的方式为谷歌汽车增强功能:
public class MyCar extends GoogleCar {
@Override
public void start() {
System.out.println("检查电量是否充足");
System.out.println("检查天气是否良好");
System.out.println("检查交通是否拥堵");
super.start();
}
}
3.装饰者模式为谷歌汽车增强功能
装饰者模式:
A.自定义一个类,实现和被增强对象上相同的接口,即:GoogleCar实现了什么接口,MyCar这个类也实现什么接口。
B.在MyCar抽取成员属性ICar car;
C.MyCar的构造函数接受ICar类型,赋值给MyCar的内部属性。
D.调用:ICar myCar = new MyCar(new GoogleCar());
public class MyCar implements ICar{
private ICar car = null;
public MyCar(ICar car) {
this.car=car;//本条语句执行后,car属性存放的就是GoogleCar对象。
}
@Override
public void start() {
System.out.println("检查电量是否充足");
System.out.println("检查天气是否良好");
System.out.println("检查车里是否有人");
car.start();
}
@Override
public void run() {
car.run();
}
@Override
public void stop() {
car.stop();
}
}
调用:Icar myCar = new MyCar(new GoogleCar());
装饰者模式的应用场景:对系统中已经存在的类/对象上的某些功能要增强,不能采用直接修改源代码/继承。
装饰者模式的要求:A.被增强的类(GoogleCar)上的方法必须实现接口中的方法。B.增强的类(MyCar)必须实现和GoogleCar相同的接口
装饰者模式的弊端:如果接口中的方法过多,代码繁冗.不易维护。即:如果ICar中方法过多,MyCar类中要写很多代码,而这些代码仅仅是对GoogleCar的简单调用。
二、动态代理
4.动态代理方式解决问题
动态代理模式本质:JDK在内存中重新生成了MyCar.class字节码文件,通过字节码文件创造了一个代理对象,代理对象上有我们需要的方法
告诉JDK3个参数:
1_:字节码加载器: 固定写法:当前类.class.getClassLoad();
ClassLoader classLoader = 类名.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader
字节码加载器:JVM中专门有一类程序,专业将硬盘上的各个字节码文件通过IO流读取到内存中去。
A.应用类加载器:自己创建的各个java文件生成的字节码。应用类AppClassLoader
B.扩展类加载器:在JVM中只要是ext(extension扩展)下的都属于扩展类,在/lib/ext包下。ExtClassLoader
C.根类类加载器:在java.lang包下的常见类,最快的方式加载到内存,通过根类类加载器,底层由C/C++代码实现的不是java实现的。null
2_:告诉JDK,GoogleCar字节码实现了那些接口
//实例化GoogleCar对象
ICar car = new GoogleCar();
//获取到的就是GoogleCar.class文件对应的对象
Class clazz.car.getClass();
Class[] interfaces = clazz.getInterfaces();
for(Class cla : interfaces){
System.out.println(cla);
//通过ICar获取到其上所有方法
Method[] methods = cla.getMethods();
for(Method md : methods){
System.out.println(md);
}
}
3_:实现InvocationHandler接口对象
Class MyProxy implements InvocationHandler{
//告诉虚拟机MyCar.java底层各个方法上start()、run()、stop()存放哪些指令
@Override
public Object invoke(Object proxy,//代理对象
Method method,//正在执行的各个方法
Object[] args//正在执行方法上的参数
) throws Throwable{
System.out.println(method.getName());
method.invoke(car);//method代表GoogleCar上正在执行的各个方法
if(method.getName().equals("start")){
System.out.println("检查天气是否良好");
}else{
method.invoke(car);
}
return null;
}
}
//以下语句执行完毕之后,JDK在内部生成了一个字节码对象,而且通过这个字节码对象创造一个代理对象
ICar car=(ICar)Proxy.newProxyInstance(
TestCar.class.getClassLoader(), //第一个参数:字节码加载器
GoogleCar.class.getInterfaces(), //第二个参数:被增强类实现的接口
new InvocationHandler() {//第三个参数:实现InvocationHandler接口对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//System.out.println(method.getName());//start run stop
if(method.getName().equals("start")){
//System.out.println(Arrays.asList(args)+" @@@@@@@@@"); args代表当前方法参数
System.out.println("电量是否OK");
System.out.println("天气是否OK");
System.out.println("交通是否OK");
//本质上在调用GoogleCar字节码上名称start的方法,将参数继续向其传递
return method.invoke(new GoogleCar(), args);
}else{
//本质上在调用GoogleCar字节码上名称不是start的方法
return method.invoke(new GoogleCar(),args);
//System.out.println(method.getName());
}
}
});
String str=car.start("aaaaa","bbbbb");
System.out.println(str+" %%%%%%%%");
car.run();
car.stop();
5.动态代理详解
1_解决了装饰者的弊端
2_本质:我们需要告诉JDK3个参数:字节码加载器,被增强对象所在的字节码实现的接口,实现了invocationhandler接口对象.JDK内存创建一个对应的字节码,并且通过这个字节码生成一个代理对象,这个代理对象上携带了我们所需要的所有的函数.
Proxy.newProxyInstance
参数1:loader,类加载器,动态代理类运行时创建。任何类都需要类加载器将其加载到内存。
一般情况:当前类.class.getClassLoader();
参数2:Class[] interfaces,代理类需要实现的所有接口
方式1:目标类实例.getClass().getInterfaces();
注意:只能获得自己的接口,不能获得父元素的接口
方式2:new Class[]{UserService.class}
例如:jdbc驱动-->DriverManager 获得接口Connection
参数3:InvocationHandler,处理类,接口,必须进行实现类,一般采用匿名内部类
提供invoke方法,代理类的每一个方法执行时,都将调用一次invoke
参数31:Object proxy:代理对象
参数32:Method method:代理对象当前执行的方法的描述对象(反射)
执行方法名:method.getName()
执行方法:method.invoke(对象,实际参数)
参数33:Object[] args:方法实际参数
6.案例动态代理完成性能监控
1_场景描述
实际开发中,都是由项目设计人员约定好各个模块的接口,然后由程序员完成这些接口功能。假如我们的项目中有有一个用户模块,在这个模块下应该有如下功能:
用户的注册/登录/添加/修改/删除/查询一个用户/查询所有用户/按照条件查询部分用户
设计人员在系统设计阶段,先设计好了以下接口
public interface IUser{
public void login();
public void register();
public void addUser();
public void delUser();
public void uptUser();
public void findOneUser();
public void findAllUser();
public void findSomelUser();
}
之后由开发人员来实现以上功能,我们正常会建立一个User接口的实现类,并实现各自功能。
代码如下:
puiblic class UserImp implements IUser{
public void login(){
System.out.println(“用户的登录功能代码”);
}
public void register(){
System.out.println(“注册用户的功能代码”);
}
public void addUser(){
System.out.println(“添加用户的功能代码”);
}
public void delUser(){
System.out.println(“删除用户的功能代码”);
}
public void uptUser(){
System.out.println(“更新用户的功能代码”);
}
public void findOneUser(){
System.out.println(“查找一个用户的功能代码”);
}
public void findAllUser(){
System.out.println(“查找所有用户的功能代码”);
}
public void findSomelUser(){
System.out.println(“查找部分用户的功能代码”);
}
}
功能制作完毕之后,项目经理有一个需求,测试每个方法的执行时间:
要求在每个方法执行完毕之后,要打印如下结果:
方法XX执行了33秒
大家可能需要在每个函数内内部来记录函数的起始结束时间,在去控制台打印当前函数的执行时间。我们发现每个方法前后写相同代码很繁琐,而且测试结束后这些代码就不需要了,此时我们如何实现?
利用动态代理实现方法的性能执行测试
2_原理分析
分析我们利用动态代理创建IUser 接口的实现类,在动态代理中将各个方法的起始和结束时间进行一个测试。这样就可动态的检测各个方法的执行时间了
3_代码实现
IUser uu=new UserImp();
IUser user=(IUser)Proxy.newProxyInstance(TestDynamicProxy.class.getClassLoader(), uu.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj=null;
long start=System.currentTimeMillis();
obj=method.invoke(uu,null);
long end=System.currentTimeMillis();
System.out.println("方法"+method.getName()+"执行了"+(end-start)+"毫秒");
return obj;
}
});
user.addUser();
user.delUser();
user.findAllUser();
user.findOneUser();
user.findSomelUser();
user.login();
user.uptUser();
====