源码:https://github.com/2020GetGoodOffer/springStudy
应用场景
生活中的租房中介,售票黄牛,婚介,经纪人,快递,事务代理,非侵入式日志监听等,是代理模式的实际体现。
代理模式的定义也很简单,是指为其他对象提供一种代理,以控制对这个对象的访问。代理模式属于结构型设计模式。
使用代理模式主要有两个目的:①保护对象②增强目标对象。
静态代理
例1
以相亲为例,顶层接口Person的代码如下:
public interface Person {
void findLove();
}
儿子要找对象,实现Son类:
public class Son implements Person {
@Override
public void findLove() {
System.out.println("儿子要求:36D");
}
}
儿子没有时间,父亲帮助儿子相亲:
public class Father {
private Son son;
public Father(Son son){
this.son=son;
}
public void findLove(){
System.out.println("父亲物色对象");
this.son.findLove();
System.out.println("儿子找对象成功");
}
}
测试代码:
public static void main(String[] args) {
//只能帮儿子找对象
Father father=new Father(new Son());
father.findLove();
}
结果如图:
例2
大家可能还是不够清楚如何将代理模式应用到业务场景中,来看一个实际业务场景。
在分布式业务场景中,通常会对数据库进行分库分表,分库分表之后使用Java操作时可能就需要配置多个数据源,我们通过设置数据源路由来动态切换数据源。
先创建Order订单类:
public class Order {
private Object objectInfo;
private Long createTime;
private String id;
public Object getObjectInfo() {
return objectInfo;
}
public void setObjectInfo(Object objectInfo) {
this.objectInfo = objectInfo;
}
public Long getCreateTime() {
return createTime;
}
public void setCreateTime(Long createTime) {
this.createTime = createTime;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
创建OrderDao持久层操作类:
public class OrderDao {
public int insert(Order order){
System.out.println("OrderDao创建Order成功");
return 1;
}
}
创建IOrderService接口:
public interface IOrderService {
int createOrder(Order order);
}
创建OrderService实现类:
public class OrderService implements IOrderService {
private OrderDao orderDao;
public OrderService(){
//如果使用Spring应该是自动注入的 模拟该过程直接初始化
orderDao=new OrderDao();
}
@Override
public int createOrder(Order order) {
System.out.println("OrderService调用orderDao创建订单");
return orderDao.insert(order);
}
}
接下来使用静态代理,主要完成的功能是:根据订单创建时间自动按年进行分库。
根据开闭原则,我们修改原来写好的代码逻辑,通过代理对象来完成。
先创建数据源路由对象,使用ThreadLocal的单例实现DynamicDataSourceEntry类:
//动态切换数据源
public class DynamicDataSourceEntry {
//默认数据源
public final static String DEFAULT_SOURCE=null;
private final static ThreadLocal<String> local=new ThreadLocal<>();
private DynamicDataSourceEntry(){}
//清空数据源
public static void clear(){
local.remove();
}
//获取当前数据源名字
public static String get(){
return local.get();
}
//还原当前切换的数据源
public static void restore(){
local.set(DEFAULT_SOURCE);
}
//设置数据源
public static void set(String source){
local.set(source);
}
//根据年份动态设置数据源
public static void set(int year){
local.set("DB_"+year);
}
}
创建切换数据源的代理类OrderServiceStaticProxy:
public class OrderServiceStaticProxy implements IOrderService {
private SimpleDateFormat format=new SimpleDateFormat("yyyy");
private IOrderService orderService;
public OrderServiceStaticProxy(IOrderService orderService){
this.orderService=orderService;
}
@Override
public int createOrder(Order order) {
before();
Long time = order.getCreateTime();
int dbRouter = Integer.parseInt(format.format(new Date(time)));
System.out.println("静待代理类自动分配到DB_"+dbRouter+"数据源处理数据");
DynamicDataSourceEntry.set(dbRouter);
orderService.createOrder(order);
after();
return 0;
}
public void before(){
System.out.println("代理前");
}
public void after(){
System.out.println("代理后");
}
}
测试代码:
public static void main(String[] args) {
try{
Order order = new Order();
SimpleDateFormat format = new SimpleDateFormat("yyy/MM/dd");
Date date = format.parse("2020/04/15");
order.setCreateTime(date.getTime());
IOrderService proxy = new OrderServiceStaticProxy(new OrderService());
proxy.createOrder(order);
}catch (Exception e){
e.printStackTrace();
}
}
运行结果:
类图关系
动态代理
动态代理和静态代理的思路基本是一致的,只不过动态代理的功能更加强大,随着业务扩展的适应力更强。
如果还以相亲为例,那么使用动态代理相当于能够适应复杂的业务场景,不仅包括父亲给儿子找对象,如果找对象这一业务发展成了一个产业,出现了媒婆、婚介所等,那么静态代理的成本就太高了,需要一个更加通用的解决方案,满足任何单身人士找对象的需求。
下面来升级一下代码。
JDK实现方式
创建媒婆类JDKMeipo:
public class JDKMeipo implements InvocationHandler {
//被代理的对象
private Object target;
public Object getInstance(Object target) throws Exception{
this.target=target;
Class<?> clazz=target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object obj = method.invoke(this.target, args);
after();
return obj;
}
private void before(){
System.out.println("我是媒婆,开始帮助你物色对象");
}
private void after(){
System.out.println("物色对象完毕");
}
}
创建单身客户类Customer:
public class Customer implements Person {
@Override
public void findLove() {
System.out.println("要求是36D");
}
}
测试代码如下:
public static void main(String[] args) {
try {
Person person = (Person) new JDKMeipo().getInstance(new Customer());
person.findLove();
} catch (Exception e) {
e.printStackTrace();
}
}
运行结果如下:
理解了以上案例,接下来再看数据源动态路由业务,创建动态代理的类OrderServiceDynamicProxy:
public class OrderServiceDynamicProxy implements InvocationHandler {
private SimpleDateFormat format=new SimpleDateFormat("yyyy");
private Object target;
public Object getInstance(Object target){
this.target=target;
Class<?> clazz=target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
before(args[0]);
Object object=method.invoke(target,args);
after();
return object;
}
public void before(Object target) throws Exception {
System.out.println("代理前");
Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
int dbRouter = Integer.parseInt(format.format(new Date(time)));
System.out.println("静待代理类自动分配到DB_"+dbRouter+"数据源处理数据");
DynamicDataSourceEntry.set(dbRouter);
}
public void after(){
System.out.println("代理后");
}
}
测试代码如下:
public static void main(String[] args) {
try{
Order order=new Order();
SimpleDateFormat format = new SimpleDateFormat("yyy/MM/dd");
Date date = format.parse("2020/04/15");
order.setCreateTime(date.getTime());
IOrderService orderService= (IOrderService) new OrderServiceDynamicProxy().getInstance(new OrderService());
orderService.createOrder(order);
}catch (Exception e){
e.printStackTrace();
}
}
运行结果如下
效果和之前一样,但是使用动态代理后,我们不仅能实现Order数据源的动态路由,还可以实现其他任何类的数据源路由。
JDK动态代理和CGLib
JDK动态代理采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的。
JDK动态代理生成对象的步骤如下:
(1)获取被代理对象的引用,并且获取它的所有接口,反射获取
(2)JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口
(3)动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码调用
(4)编译新生成的Java代码的.class文件
(5)重新加载到JVM中运行
JDK动态代理和CGLib:
(1)JDK动态代理实现了被代理对象的接口,CGLib代理继承了被代理对象
(2)JDK动态代理和CGLib代理都在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架写Class字节码,CGLib代理实现更复杂,生成代理类比JDK动态代理效率低
(3)JDK动态代理调用代理方法是通过反射机制调用的,CGLib是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高
代理模式与Spring
先看ProxyFactoryBean的核心方法getObject(),源码如下:
@Nullable
public Object getObject() throws BeansException {
this.initializeAdvisorChain();
if (this.isSingleton()) {
return this.getSingletonInstance();
} else {
if (this.targetName == null) {
this.logger.warn("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
}
return this.newPrototypeInstance();
}
}
在getObject()方法中,主要调用getSingletonInstance和newPrototypeInstance。在Spring的配置中如果不做任何配置,那么Spring代理生成的Bean都是单例对象,如果修改scope,则每次创建一个新的原型对象。
Spring利用动态代理实现AOP时有两个非常重要的类:JdkDynamicAopProxy和CglibAopProxy类:
Spring中的代理选择原则:
(1)当Bean有实现接口时,选择JDK动态代理
(2)当Bean没有实现接口时,选择CGLib代理
(3)Spring可以通过配置强制使用CGLib代理,只需要在Spring的配置文件中加入:<aop:aspect-autoproxy proxy-target-class="true">
静态代理和动态代理的区别
(1)静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违反开闭原则
(2)动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则
(3)若动态代理要对模板类的增强逻辑进行扩展,结合策略模式,只需要新增策略类即可,无需修改代理类的代码
代理模式的优缺点
优点:
(1)能将代理对象与真是被调用的目标对象分离
(2)在一定程度上降低了系统的耦合性,扩展性好
(3)可以起到保护目标对象的作用
(4)可以增强目标对象的功能
缺点:
(1)会造成系统设计中类的数量的增加
(2)在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢
(3)增加了系统的复杂度