动态代理[拓展:重难点]
一、概述、快速入门
1、代理是啥?
- 指的是某些场景下对象会找一个代理对象,来辅助自己完成一些工作。
- 比如歌星找经纪人、买房的人找房产中介。
2、代理主要干什么?如何工作的?
-
举例说明:
-
比如对象是杨超越,她还没有火之前,别人叫她去唱歌、跳舞,都是直接叫杨超越对象本人的;
-
那她火了之后,别人想要叫她去唱歌、跳舞,就不能直接去联系杨超越(对象)本人了,而是联系杨超越(对象)的经纪人(代理),那她的经纪人就要收别人的首付款,等表演完,再收尾款。
-
-
代理的工作流程:
代理主要是对 对象的行为额外做一些辅助操作。
3、如何创建代理对象
-
Java中代理的代表类是:java.lang.reflect.Proxy。
-
Proxy提供了一个静态方法,用于为对象产生一个代理对象返回。
(1)范例
package com.app.d10_proxy;
/**
* 1、创建接口:规定明星对象的行为方法
*/
public interface Skill {
void sing(); // 唱歌
void jump(); // 跳舞
}
package com.app.d10_proxy;
/**
* 2、创建明星类:实现Skill接口,实现明星的行为方法
*/
public class Star implements Skill {
private String name; // 明星的姓名
public Star(){
}
public Star(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* a.实现明星的唱歌行为
*/
@Override
public void sing() {
System.out.println(name + "开始唱歌:我和我的祖国,一刻都不能分离~~");
}
/**
* b.实现明星的跳舞行为
*/
@Override
public void jump() {
System.out.println(name + "开始跳舞,跳的很迷人~~");
}
}
package com.app.d10_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 5、创建明星代理类
*/
public class StarAgentProxy {
/**
* a.定义一个静态方法,为明星对象生成一个代理对象(经纪人)返回
* @param star 用于接收一个明星对象
* @return 返回一个代理对象
*/
public static Skill getProxy(Star star) {
// (1) 为明星对象,生成一个代理对象,返回
/**
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h)
* 参数一:定义代理类的类加载器
* 参数二:代理类要实现的接口列表
* 参数三:要将方法调用分派到的调用处理程序(代理对象的核心处理程序)
*/
return (Skill) Proxy.newProxyInstance(star.getClass().getClassLoader(),
star.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("经纪人收首付款~~");
// (2) 真正的让明星对象去唱歌、跳舞
/**
* method: 正在调用的方法对象
* args: 被调用的方法对象的参数
*/
Object rs = method.invoke(star, args);
System.out.println("经纪人收尾款,把" + star.getName() + "接回来了~~");
return rs; // 如果该方法有返回值,则返回结果;如果该方法无返回值,则返回null
}
});
}
}
package com.app.d10_proxy;
/**
* 目标:学习开发出一个动态代理的对象出来,理解动态代理的执行流程。
*/
public class Test {
public static void main(String[] args) {
// 3、创建一个明星对象(杨超越)
Star star = new Star("杨超越");
// 4、杨超越没有火之前,直接调用她去唱歌、跳舞
star.sing();
System.out.println();
star.jump();
System.out.println("---------------------------");
// 6、杨超越火了之后,想要调用她去唱歌、跳舞,必须经过她的代理对象(经纪人)
Skill proxy = StarAgentProxy.getProxy(star);
proxy.sing();
System.out.println();
proxy.jump();
}
}
杨超越开始唱歌:我和我的祖国,一刻都不能分离~~
杨超越开始跳舞,跳的很迷人~~
---------------------------
经纪人收首付款~~
杨超越开始唱歌:我和我的祖国,一刻都不能分离~~
经纪人收尾款,把杨超越接回来了~~
经纪人收首付款~~
杨超越开始跳舞,跳的很迷人~~
经纪人收尾款,把杨超越接回来了~~
Process finished with exit code 0
(2)Java中生成代理,并指定代理做什么事
总结
-
代理是什么?
- 一个对象,用来对被代理对象的行为额外做一些辅助工作。
-
在Java中实现动态代理的步骤是?
-
必须定义接口
-
被代理对象需要实现这个接口
-
使用Proxy类提供的方法,得到对象的代理对象
-
-
通过代理对象调用方法,执行流程是?
- 先走向代理
- 代理可以为方法额外做一些辅助工作
- 开发真正触发对象的方法的执行
- 回到代理中,由代理负责返回结果给方法的调用者
- 以后我们要学习的spring框架,它也是基于动态代理的形式返回一个更强的对象给我们使用。
二、应用案例
1、做性能分析
-
只是模拟场景,不需要完全把功能实现出来,目的就是为了
理解动态代理的优势
。 -
需求:
- 模拟某企业用户管理业务,需包含用户登录、删除、查询功能,并要统计每个功能的耗时。
-
分析:
- 1、定义一个UserService表示用户业务接口,规定必须完成用户登录、删除、查询功能。
- 2、定义一个实现类UserServiceImpl实现UserService接口,并完成相关功能,且统计每个功能的耗时。
- 3、定义测试类,创建实现类对象,调用方法。
-
实现:
package com.app.d11_proxy_test2; /** * 1、定义用户业务接口:规定必须完成用户登录、删除、查询的功能 */ public interface UserService1 { String userLogin(String loginName, String passWord); // 用户登录功能 void deleteUser(); // 删除用户功能 void selectUser(); // 查询用户功能 }
package com.app.d11_proxy_test2; /** * 2、定义实现类:实现用户业务接口的用户登录、删除、查询的功能 */ public class UserServiceImpl1 implements UserService1{ /** * 实现用户登录功能 * @param loginName 登录名 * @param passWord 登录密码 * @return 返回登录结果 */ @Override public String userLogin(String loginName, String passWord) { // 记录方法执行开始时间 long startTime = System.currentTimeMillis(); String rs = "登录名或密码错误!"; if ("admin".equals(loginName) && "abc123".equals(passWord)) { System.out.println(loginName + "登录成功~~"); try { // 让该线程睡眠1秒,模拟登录耗时 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } // 记录方法执行结束时间 long endTime = System.currentTimeMillis(); // 输出执行此方法的耗时 System.out.println("userLogin方法耗时:" + (endTime - startTime) / 1000.0 + "秒"); return rs; // 返回登录结果 } @Override public void deleteUser() { // 记录方法执行开始时间 long startTime = System.currentTimeMillis(); try { // 让该线程睡眠2.5秒,模拟删除用户耗时 System.out.println("正在删除用户数据....."); Thread.sleep(2500); } catch (InterruptedException e) { e.printStackTrace(); } // 记录方法执行结束时间 long endTime = System.currentTimeMillis(); // 输出执行此方法的耗时 System.out.println("deleteUser方法耗时:" + (endTime - startTime) / 1000.0 + "秒"); } @Override public void selectUser() { // 记录方法执行开始时间 long startTime = System.currentTimeMillis(); try { // 让该线程睡眠3秒,模拟删除用户耗时 System.out.println("查询了10000个用户!"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 记录方法执行结束时间 long endTime = System.currentTimeMillis(); // 输出执行此方法的耗时 System.out.println("selectUser方法耗时:" + (endTime - startTime) / 1000.0 + "秒"); } }
package com.app.d11_proxy_test2; /** * 3、定义测试类 */ public class Test1 { public static void main(String[] args) { // 创建实现类对象,调用方法 UserService1 user = new UserServiceImpl1(); user.userLogin("admin", "abc123"); user.deleteUser(); user.selectUser(); } }
admin登录成功~~ userLogin方法耗时:1.012秒 正在删除用户数据..... deleteUser方法耗时:2.502秒 查询了10000个用户! selectUser方法耗时:3.009秒 Process finished with exit code 0
(1)本案例存在哪些问题?
- 业务对象的每个方法都要进行性能统计,存在大量重复的代码。
- 如果又有新业务的功能,需要添加方法,还要写一次测试性能的代码,如果有一大堆业务呢?是不是大大降低了开发效率。
(2)使用动态代理优化本案例
-
关键步骤:
-
1、必须有接口,实现类要实现接口(代理通常是基于接口实现的)。
-
3、创建一个实现类的对象,该对象为业务对象,紧接着为业务对象做一个代理对象。
-
package com.app.d11_proxy_test2;
/**
* 1、定义用户业务接口,规定必须完成用户登录、删除、查询功能
*/
public interface UserService2 {
String userLogin(String loginName, String passWord); // 用户登录
void deleteUser(); // 用户删除
void selectUser(); // 用户查询
}
package com.app.d11_proxy_test2;
/**
* 2、定义实现类:实现用户业务接口
*/
public class UserServiceImpl2 implements UserService2 {
/**
* 实现用户登录功能
*/
@Override
public String userLogin(String loginName, String passWord) {
String rs = "登录名或密码错误!";
if ("admin".equals(loginName) && "abc123".equals(passWord)) {
System.out.println(loginName + "登录成功~");
}
try {
// 让该线程睡眠1秒,模拟登录花费时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return rs;
}
/**
* 实现用户删除功能
*/
@Override
public void deleteUser() {
try {
// 让该线程睡眠2.5秒,模拟删除很多用户的场景
System.out.println("正在删除用户数据...");
Thread.sleep(2500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 实现用户查询功能
*/
@Override
public void selectUser() {
System.out.println("查询了10000多个用户!");
try {
// 让该线程睡眠3秒,模拟查询很多用户的场景
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.app.d11_proxy_test2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 3、定义对象代理类:负责统计每个功能的耗时
*/
public class ProxyUtil {
/**
* a.定义一个静态方法,为用户业务对象生成一个代理对象返回
*/
public static UserService2 getProxy(UserServiceImpl2 userService) {
// (1) 为对象生成一个代理对象并返回
return (UserService2) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// (1) 记录开始时间
long startTime = System.currentTimeMillis();
// (2) 真正调用对象方法
Object rs = method.invoke(userService, args);
// (3) 记录结束时间
long endTime = System.currentTimeMillis();
// (4) 输出执行该方法的耗时
System.out.println(method.getName() + "方法耗时:" + (endTime - startTime) / 1000.0 + "秒");
// (5) 如果该方法有返回值,则返回结果;否则返回null。
return rs;
}
});
}
}
package com.app.d11_proxy_test2;
/**
* 目标:掌握使用动态代理解决问题,理解使用动态代理的优势。
*/
public class Test2 {
public static void main(String[] args) {
// 4、创建一个用户
// UserServiceImpl2 user = new UserServiceImpl2();
// 5、获取一个动态代理
// UserService2 proxy = ProxyUtil.getProxy(user);
UserService2 proxy = ProxyUtil.getProxy(new UserServiceImpl2());
// 6、调用方法,测试用户业务功能的性能
proxy.userLogin("admin", "abc123");
proxy.deleteUser();
proxy.selectUser();
}
}
admin登录成功~
userLogin方法耗时:1.016秒
正在删除用户数据...
deleteUser方法耗时:2.501秒
查询了10000多个用户!
selectUser方法耗时:3.002秒
Process finished with exit code 0
(3)动态代理的优势
-
可以在不改变方法源码的情况下,实现对方法功能的增强,提高了代码的复用性。
-
简化了编程工作,提高了开发效率,同时提高了软件系统的可扩展性。
-
可以为被代理对象的所有方法做代理。
-
非常的灵活,支持
任意接口类型
的实现类对象
做代理,也可以直接为接口本身做代理(后面的内容)
。