动态代理介绍、准备功能
这节课我们学习一个Java的高级技术叫做动态代理。首先我们认识一下代理长什么样?
假设现在有一个明星坤坤,它有唱歌和跳舞的本领,作为明星是要用唱歌和跳舞来赚钱的,但是每次做节目,唱歌的时候要准备话筒、收钱,再唱歌;跳舞的时候也要准备场地、收钱、再唱歌。明星觉得我擅长的做的事情是唱歌,和跳舞,但是每次唱歌和跳舞之前或者之后都要做一些繁琐的事情,有点烦。于是就找个一个经济公司,请了一个代理人,代理明星处理这些事情,如果有人想请明星演出,直接找代理人就可以了。如下图所示
我们说明星的代理是中介公司派的,那中介公司怎么知道,要派一个有唱歌和跳舞功能的代理呢?
解决这个问题,Java使用的是接口,明星想找代理,在Java中需要明星实现了一个接口,接口中规定要唱歌和跳舞的方法。Java就可以通过这个接口为明星生成一个代理对象,只要接口中有的方法代理对象也会有。
接下来我们就先把有唱歌和跳舞功能的接口,和实现接口的大明星类定义出来,声明接口是为了声明大明星类中有的方法代理对象也会有,注意,大明星类也需要实现Star接口,这是java生成代理的一个约定。
生成动态代理对象
有了上面的准备工作,下面我们需要写一个为BigStar生成动态代理对象的工具类ProxyUtil代表中介机构。使用工具类产生代理则需要用Java为开发者提供的一个生成代理对象的类叫Proxy类。注意Proxy类有多个,我们需要选择java.lang.reflect中的Proxy
通过Proxy类的newInstance(…)方法可以为实现了同一接口的类生成代理对象。 调用方法时需要传递三个参数,该方法的参数解释可以查阅API文档,如下。
这里代码逻辑比较抽象,所以写了大量的注释来解释逻辑,需要仔细阅读。
public class ProxyUtil {
//因为是工具类所以可以定义一个静态方法来产生代理,产生谁的代理可以通过方法参数传递
//这个方法生成的代理肯定是实现了Star接口的对象 所以这里可以将Star作为返回值
public static Star createProxy(BigStar bigStar){//我们要为bigStar创造代理并返回
/* newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数1:用于指定一个类加载器 用于加载生成的代理类 写法是固定的 背就行 一般用当前类的类加载器
参数2:一个接口数组 指定生成的代理长什么样子 也就是有哪些方法 我们这里只有一个接口 把它包装成数组传进去即可
参数3:用来指定生成的代理对象要干什么事情 这里传递的是一个InvocationHandler接口
因为接口不能直接创建对象 所以一般是传递一个匿名内部类对象来指定代理对象干什么事情重写invoke方法就行
*/
/* invoke方法是个回调方法 会被谁回调呢? 假设代理写好了 调用时是会写这样的代码的:
* Star starProxy = ProxyUtil.createProxy(s);//得到一个s的代理对象
* starProxy.sing("好日子") starProxy.dance() 而sing和dance会调用invoke方法!
* 因为代理干什么事情用invoke决定 invoke需要三个参数 所以sing和dance也会传进这三个参数
* 比如starProxy.sing("好日子") starProxy是第一个参数 sing是第二个 "好日子"是第三个
* 第一个参数java把代理对象当做一个Object 也就是starProxy 第二个参数是调用的方法
* 如果是sing调用 method代表的就是sing方法 第三个参数args会把方法的参数通过一个object数组传进来
* 比如sing调用时就会把"好日子"传进数组 这就是invoke三个参数的含义
*/
//newProxyInstance返回的是Object 所以需要强转
Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Star.class}, new InvocationHandler() {
@Override // 重写invoke回调方法
//invoke是重点 代理干什么事情其实是由它决定
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);//bigStar代表明星 再把调用方法参数传进来
//注意这里的invoke不是这里写的invoke 而是反射里Method提供的invoke方法!
//调用sing 则会返回"谢谢大家!
}
});
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();
}
}
运行结果:
动态代理应用
学习完动态代理的基本使用之后,接下来我们再做一个应用案例。
现有如下代码
/**
* 用户业务接口
*/
public interface UserService {
// 登录功能
void login(String loginName,String passWord) throws Exception;
// 删除用户
void deleteUsers() throws Exception;
// 查询用户,返回数组的形式。
String[] selectUsers() throws Exception;
}
下面有一个UserService接口的实现类,下面每一个方法中都有计算方法运行时间的代码。
/**
* 用户业务实现类(面向接口编程)
*/
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("----------------------------------");
}
}
执行结果如下所示: