动态代理介绍
用一个明星的案例来解释动态代理的流程。
- 假设现在有一个明星坤坤,它有唱歌和跳舞的本领,作为明星是要用唱歌和跳舞来赚钱的。
- 但是每次做节目,唱歌的时候要准备话筒、收钱,再唱歌;跳舞的时候也要准备场地、收钱、再唱歌。
- 明星觉得我擅长的做的事情是唱歌,和跳舞,但是每次唱歌和跳舞之前或者之后都要做一些繁琐的事情,有点烦。
- 于是就找个一个经济公司,请了一个代理人,代理明星处理这些事情,如果有人想请明星演出,直接找代理人就可以了。
明星代理案例实现
根据上面的案例,可以设计一个大明星对象,来表示大明星的职责是唱歌和跳舞;声明一个明星接口来声明大明星类中有的方法代理对象也会有,大明星对象实现明显接口。
- 明星接口
public interface Star {
String sing(String name);
void dance();
}
- 大明星对象
public class BigStar implements Star{
private String name;
public BigStar(String name) {
this.name = name;
}
public String sing(String name){
System.out.println(this.name + "正在唱:" + name);
return "谢谢!谢谢!";
}
public void dance(){
System.out.println(this.name + "正在优美的跳舞~~");
}
}
- 生成动态代理对象
有了上面的准备工作,需要写一个为BigStar生成动态代理对象的工具类ProxyUtil代表中介机构。使用工具类产生代理则需要用Java为开发者提供的一个生成代理对象的类叫Proxy类。注意Proxy类有多个,我们需要选择java.lang.reflect中的Proxy
通过Proxy类的newInstance(…)方法可以为实现了同一接口的类生成代理对象。 调用方法时需要传递三个参数,该方法的参数解释可以查阅API文档,如下
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static Star createProxy(BigStar bigStar){
/* newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数1:用于指定一个类加载器
参数2:指定生成的代理长什么样子,也就是有哪些方法
参数3:用来指定生成的代理对象要干什么事情
*/
// Star starProxy = ProxyUtil.createProxy(s);
// starProxy.sing("好日子") starProxy.dance()
Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Star.class}, new InvocationHandler() {
@Override // 回调方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理对象要做的事情,会在这里写代码
if(method.getName().equals("sing")){
System.out.println("准备话筒,收钱20万");
}else if(method.getName().equals("dance")){
System.out.println("准备场地,收钱1000万");
}
return method.invoke(bigStar, args);
}
});
return starProxy;
}
}
- 写一个Test类调用我们写好的ProxyUtil工具类,为BigStar对象生成代理对象
public class Test {
public static void main(String[] args) {
BigStar s = new BigStar("大明星坤坤");
Star starProxy = ProxyUtil.createProxy(s);
String rs = starProxy.sing("好日子");
System.out.println(rs);
starProxy.dance();
}
}
- 运行结果如下
案例分析
Proxy类的newInstance(…)方法
newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数1:用于指定一个类加载器 用于加载生成的代理类 写法是固定的 背就行 一般用当前类的类加载器
参数2:一个接口数组 指定生成的代理长什么样子 也就是有哪些方法 我们这里只有一个接口 把它包装成数组传进去即可
参数3:用来指定生成的代理对象要干什么事情 这里传递的是一个InvocationHandler接口,因为接口不能直接创建对象 所以一般是传递一个匿名内部类对象来指定代理对象干什么事情重写invoke方法就行
Proxy代理对象的执行流程
nvoke方法是个回调方法 会被谁回调呢?
1. 假设代理写好了调用时是会写这样的代码的:
//得到一个s的代理对象
Star starProxy = ProxyUtil.createProxy(s);
starProxy.sing("好日子")
starProxy.dance()
2. 这两个sing和dance方法会调用invoke方法!
因为代理干什么事情用invoke决定,invoke需要三个参数,所以sing和dance也会传进这三个参数
比如starProxy.sing("好日子") starProxy是第一个参数,sing是第二个,"好日子"是第三个
第一个参数java把代理对象当做一个Object:也就是starProxy
第二个参数是调用的方法:如果是sing调用 method代表的就是sing方法
第三个参数args:会把方法的参数通过一个object数组传进来(比如sing调用时就会把"好日子"传进数组)
这就是invoke三个参数的含义
动态代理应用场景
某系统有一个用户管理类,包含用户登录,删除用户,查询用户等功能,系统要求统计每个功能的执行耗时情况,以便后期观察程序性能。
现有如下代码:
用户业务接口UserService
/**
* 用户业务接口
*/
public interface UserService {
// 登录功能
void login(String loginName,String passWord) throws Exception;
// 删除用户
void deleteUsers() throws Exception;
// 查询用户,返回数组的形式。
String[] selectUsers() throws Exception;
}
用户业务实现类UserServiceImpl
/**
* 用户业务实现类(面向接口编程)
*/
public class UserServiceImpl implements UserService {
@Override
public void login(String loginName, String passWord) throws Exception {
long time1 = System.currentTimeMillis();
if ("admin".equals(loginName) && "123456".equals(passWord)) {
System.out.println("您登录成功,欢迎光临本系统~");
} else {
System.out.println("您登录失败,用户名或密码错误~");
}
Thread.sleep(1000);
long time2 = System.currentTimeMillis();
System.out.println("login方法耗时:" + (time2 - time1) / 1000.0 + "s");
}
@Override
public void deleteUsers() throws Exception {
long time1 = System.currentTimeMillis();
System.out.println("成功删除了1万个用户~");
Thread.sleep(1500);
long time2 = System.currentTimeMillis();
System.out.println("deleteUsers方法耗时:" + (time2 - time1) / 1000.0 + "s");
}
@Override
public String[] selectUsers() throws Exception {
long time1 = System.currentTimeMillis();
System.out.println("查询出了3个用户");
String[] names = {"张全蛋", "李二狗", "牛爱花"};
Thread.sleep(500);
long time2 = System.currentTimeMillis();
System.out.println("selectUsers方法耗时:" + (time2 - time1) / 1000.0 + "s");
return names;
}
}
会发现每一个方法中计算耗时的代码都是重复的,况且这些重复的代码并不属于UserSerivce的主要业务代码。所以接下来可以把把计算每一个方法的耗时操作,交给代理对象来做。
先在UserService类中把计算耗时的代码删除,代码如下
public class UserServiceImpl implements UserService {
@Override
public void login(String loginName, String passWord) throws Exception {
if ("admin".equals(loginName) && "123456".equals(passWord)) {
System.out.println("您登录成功,欢迎光临本系统~");
} else {
System.out.println("您登录失败,用户名或密码错误~");
}
Thread.sleep(1000);
}
@Override
public void deleteUsers() throws Exception {
System.out.println("成功删除了1万个用户~");
Thread.sleep(1500);
}
@Override
public String[] selectUsers() throws Exception {
System.out.println("查询出了3个用户");
String[] names = {"张全蛋", "李二狗", "牛爱花"};
Thread.sleep(500);
return names;
}
}
然后为UserService生成一个动态代理对象,在动态代理中调用目标方法,在调用目标方法之前和之后记录毫秒值,并计算方法运行的时间。代码如下
public class ProxyUtil {
public static UserService creatProxy(UserService userService){
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{UserService.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName=method.getName();
if(methodName.equals("login")||methodName.equals("deleteUsers")||methodName.equals("selectUsers")){
long startTime = System.currentTimeMillis();
Object rs = method.invoke(userService,args);
long endTime = System.currentTimeMillis();
System.out.println(methodName + "方法执行耗时:"+(endTime-startTime)/1000.0+"s");
return rs;
}else{
Object rs = method.invoke(userService,args);
return rs;
}
}
});
return userServiceProxy;
}
}
在测试类中为UserService创建代理对象:
public class Test {
public static void main(String[] args) throws Exception{
// 1、创建用户业务对象。
UserService userService = ProxyUtil.createProxy(new UserServiceImpl());
// 2、调用用户业务的功能。
userService.login("admin", "123456");
System.out.println("----------------------------------");
userService.deleteUsers();
System.out.println("----------------------------------");
String[] names = userService.selectUsers();
System.out.println("查询到的用户是:" + Arrays.toString(names));
System.out.println("----------------------------------");
}
}
执行结果如下所示: