文章目录
- 一、动态代理概述
- 1.1 代理的概述和作用
- 1.2 动态代理的优点
- 1.3 代理对象的创建
- 1.4 代理对象调用方法的执行流程
- 二、动态代理举例
- 2.1 歌手经纪人
- 2.2 业务功能的性能统计
- 2.3 动态代理在 Spring 框架中的应用
- 三、基于子类的动态代理
一、动态代理概述
1.1 代理的概述和作用
-
什么是代理?
代理指某些场景下对象会找一个代理对象,来辅助自己完成一些工作。如:歌星(经济人),买房的人(房产中介) -
代理主要干什么工作,是如何工作的?
是对对象的行为做一些辅助的操作。 -
代理举例:
歌手刚出道时,有人花钱让他唱歌,承诺先付首款再付尾款。那么,歌手的整个工作的流程是:收首款、唱歌、收尾款。
歌手成名以后,业务越来越多,开始雇佣了经纪人。经纪人主要负责:收首款、收尾款以及调用歌手去唱歌,歌手只负责唱歌。
1.2 动态代理的优点
- 非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接本身做代理。
- 可以为被代理对象的所有方法做代理。
- 不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。
- 可以在不改变方法源码的情况下,实现对方法功能的增强。
方法增强的理解:歌手原本的工作是唱歌,唱歌前后的收首款和尾款方法就相当于对原本的方法的增强。
1.3 代理对象的创建
-
java 中代理的代表类是:
java.lang.reflect.Proxy
-
Proxy
提供了一个静态方法,用于为对象产生一个代理对象返回。
参数二理解:因为客户是通过代理对象去调用歌手的唱歌方法,因此代理类需要接口的列表。
1.4 代理对象调用方法的执行流程
- 先走向代理
- 代理可以为方法额外做一些辅助工作
- 开发真正触发对象的方法的执行
- 回到代理中,由代理负责返回结果给方法的调用者
二、动态代理举例
2.1 歌手经纪人
项目包结构:
技能接口:
public interface Skill {
void jump();
void sing();
}
明星类:
public class Star implements Skill{
private String name;
public Star(String name) {
this.name = name;
}
@Override
public void jump() {
System.out.println(name + "开始跳舞");
}
@Override
public void sing() {
System.out.println(name + "开始唱歌");
}
}
明星代理类:
public class StarAgentProxy {
/**
* 设计一个方法来返回 一个明星对象 的 代理对象
* 参数一:定义代理类的类加载器
* 参数二:代理类要实现的接口列表
* 参数三:将方法调用分派到的处理程序
*/
public static Skill getProxy(Star obj){
// 为张三这个对象,生成代理对象
return (Skill) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("收首款");
// proxy 代理对象的引用
// method 正在调用的方法对象
// args 代表这个方法的参数
Object rs = method.invoke(obj, args); // 采用反射机制,如果没有返回值,则返回null
System.out.println("收尾款");
return rs;
}
});
}
}
客户端模拟:
public class Test {
/**
* 理解动态代理
*/
public static void main(String[] args) {
// 1. 创建一个对象
Star star = new Star("张三");
// 为张三对象,生成一个代理对象(经纪人)
Skill proxy = StarAgentProxy.getProxy(star);
proxy.jump();
System.out.println("--------");
proxy.sing();
}
}
输出结果:
2.2 业务功能的性能统计
需求: 模拟某企业用户管理业务,需包含用户登录、删除、查询功能,并要统计每个功能的耗时
项目包结构:
用户业务层接口:
public interface UserService {
String login(String loginName, String password);
void delete(Integer id);
void selectUsers();
}
用户业务层实现类:
public class UserServiceImpl implements UserService{
@Override
public String login(String loginName, String password) {
String rs = "登录名称或者密码错误!";
if("admin".equals(loginName) && "123456".equals(password)){
rs = "登录成功";
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return rs;
}
@Override
public void delete(Integer id) {
try {
System.out.println("正在删除数据中");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void selectUsers() {
System.out.println("查询了100个用户数据!");
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
代理对象工具类:
public class ProxyUtil {
/**
* 通过静态方法为用户业务对象返回代理对象
*/
public static UserService getProxy(UserService obj){
return (UserService) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
// 真正触发对象的行为执行
Object rs = method.invoke(obj, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "方法耗时:" + (endTime-startTime)/1000.0 + "s");
return rs;
}
});
}
}
客户端模拟:
public class Test {
public static void main(String[] args) {
UserService proxy = ProxyUtil.getProxy(new UserServiceImpl());
proxy.login("admin", "123456");
proxy.delete(1);
proxy.selectUsers();
}
}
输出结果:
2.3 动态代理在 Spring 框架中的应用
转账方法的事务问题回顾:
参考:Spring 从入门到精通系列 09 —— 转账方法的事务问题与动态代理
转账业务流程中,如果中间的某一部分业务出现异常,那么会导致异常后的事务不会执行,从而引发账户出错的严重情况。
问题的关键在于:
整个业务方法一共获取了四次数据库连接对象,有四个业务需要处理。当前事务完成后,会直接提交事务。那么当某个事务出现异常时,只对他自己的事务进行回滚,对其他的事务不回滚。
当时提出的解决方案是:
由于整体的业务属于一个线程,那么通过使用 ThreadLocal 对象把 Connection 连接对象和当前线程绑定,即使一个线程中只有一个 Connection 对象,而不是原本的四个。(要么都发生,要么都不发生)
更新事务控制后,具体实现如下:
代码变的很复杂,而且每个方法都需要加上:开启事务、提交事务…等事务处理。因此,可利用动态代理的技术进行处理。
Spring 的 AOP 的实现思想就是动态代理,即 在不修改源码的基础上对已有方法进行增强。
三、基于子类的动态代理
上文讲述的代理模式属于 基于接口的动态代理模式,当其不实现任何接口时,该动态代理对象不能得到。
但是 基于子类的动态代理 可以实现不用实现接口的情况下实现代理。
导入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
明星类:
public class Star{
private String name;
public Star() {
}
public Star(String name) {
this.name = name;
}
public void jump() {
System.out.println(name + "开始跳舞");
}
public void sing() {
System.out.println(name + "开始唱歌");
}
}
明星代理类:
public class ProxyUtil {
/**
* 通过静态方法为用户业务对象返回代理对象
*/
public static Star getProxy(Star obj){
return (Star) Enhancer.create(obj.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("收首款");
Object rs = method.invoke(obj, args);
System.out.println("收尾款");
return rs;
}
});
}
}
客户端模拟:
public class Test {
public static void main(String[] args) {
final Star star = new Star("张三");
Star proxy = ProxyUtil.getProxy(star);
proxy.jump();
System.out.println("--------");
proxy.sing();
}
}
输出结果:
注:基于子类的动态代理,被代理类必须实现无参构造(当被代理类中有参构造函数时,需重写无参构造),否则报以下异常。