我们都知道SpringAOP是用代理模式实现,那么到底是如何实现的呢?我们来探一探究竟,并手写部分代码。
定义代理模式(Proxy Pattern):是指为其他对象提供一种代理、来控制这个对象的访问。 代理对象在客户端和目标对象之间起到一种中介作用。
目的:1.保护目标对象: 我们不直接访问被代理对象,而是通过中间媒介去访问。 2.增强目标对象: 参考SpringAop的功能。
场景:生活中场景: 租房中介、售票黄牛、婚介、经纪人、快递、事务代理、非侵入式日志监听等,这些都是代理模式的实际体现。 如何理解: 租房,房屋拥有者将想出租自己多余的房子,将房子托付给房屋中介,房屋中介就代理了这个房子,我们如果想要租房子就直接去找中介即可。
代理模式主要就是增加一个中间层,来实现代理与被代理之间的组合。
我们来看看类图结构: Subject是顶层接口,RealSubject是真实的对象(被代理对象),Proxy是代理对象,代理对象拥有被代理对象的引用,客户端调用代理对象方法,同时也调用了被代理对象方法,但是在代理对象前后增加一些处理。在代码编写中,我们想到代理就会理解为是代码增强,其实就是在原本逻辑上增加一些前后逻辑,而调用者感知不到。代理模式属性结构型代理,有静态代理和动态代理
这里使用业务场景来帮助理解: 在分布式业务场景中,我们通常会对数据库进行分库分表,分库分表之后使用Java操作时,就可能需要配置多个数据源,我们通过设置数据源路由来动态切换数据源。
1.创建订单实体Order:
@Data
public class Order {
//订单信息
private Object orderInfo;
//订单创建时间
private Long createTime;
private String id;
}
2.创建OrderDao
//持久层,这里就直接写实现类了。
public class OrderDao {
public int insert(Order order){
System.out.println("OrderDao 创建Order成功");
return 1;
}
}
3.创建IOrderService接口
public interface IOrderService {
int createOrder(Order order);
}
4.创建OrderService实现类
public class OrderService implements IOrderService{
private OrderDao orderDao;
//模拟spring的注入 直接创建对象
public OrderService(){
orderDao = new OrderDao();
}
@Override
public int createOrder(Order order) {
System.out.println("调用OrderDao 创建订单");
return orderDao.insert(order);
}
}
咱们使用静态代理来进行切换数据源,依据开闭原则,写好的代理逻辑不去改变,通过代理对象来完成。先创建数据源路由对象,我们使用ThreadLocal的单例实现,DynamicDataSourceEntry类 5.DynamicDataSourceEntry
//动态切换数据源
public class DynamicDataSourceEntry {
// 默认数据源
public final static String DEFAULT_SOURCE = null;
private final static ThreadLocal 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); }
}
6.创建代理对象OrderServiceStaticProx 实现被代理的对象。
public class OrderServiceStaticProx implements IOrderService {
//日期格式化
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
//代理对象
private IOrderService orderService;
//模拟spring注入
public OrderServiceStaticProx(IOrderService orderService) {
this.orderService = orderService;
}
public int createOrder(Order order) {
before();
Long time = order.getCreateTime();
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据。");
//设置数据源
DynamicDataSourceEntry.set(dbRouter);
orderService.createOrder(order);
after();
return 0;
}
private void before() {
System.out.println("Proxy before method.");
}
private void after() {
System.out.println("Proxy after method.");
}
}
测试代码:
public static void main(String[] args) throws ParseException {
Order order = new Order();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date = sdf.parse("2020/03/11");
order.setCreateTime(date.getTime());
IOrderService orderService = new OrderServiceStaticProx(new OrderService());
orderService.createOrder(order);
}
测试结果:
Proxy before method.
静态代理类自动分配到【DB_2020】数据源处理数据。
调用OrderDao 创建订单
OrderDao 创建Order成功
Proxy after method.
符合我们预期的效果,通过代理来达到切换数据源的效果,并增加了before和after方法。 我们看一看类图是否符合我们的上文中写到的:
动态代理实际上思路是与静态代理一致的,只不过动态代理的功能更强大,扩展性更强: 我们来看一看JDK动态代理的实现原理: 1、拿到被代理类的引用,并且获取它的所有的接口(反射获取)。 2、JDK Proxy类重新生成一个新的类,实现了被代理类所有 接口的方法。 3、动态生成Java代码,把增强逻辑加入到新生成代码中。 4、编译生成新的Java代码的class文件。 5、加载并重新运行新的class,得到类就是全新类。
我们按照以上思路来对上文静态代理做一些改进:修改OrderServiceStaticProx类
public class OrderServiceDynamicProx implements InvocationHandler {
private SimpleDateFormat yearFormat = 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 Throwable {
//执行前逻辑
before(args[0]);
//被代理对象方法
Object object = method.invoke(target, args);
//执行后逻辑
after();
return object;
}
private void before(Object target) {
try {
System.out.println("Proxy before method.");
Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("动态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据。");
DynamicDataSourceEntry.set(dbRouter);
} catch (Exception e) {
e.printStackTrace();
}
}
private void after() {
System.out.println("Proxy after method.");
}
}
测试代码:
public static void main(String[] args) {
try {
Order order = new Order();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date = sdf.parse("2020/03/31");
order.setCreateTime(date.getTime());
IOrderService orderService = (IOrderService) new OrderServiceDynamicProx().getInstance(new OrderService());
orderService.createOrder(order);
} catch (Exception e) {
e.printStackTrace();
}
}
测试结果
Proxy before method.
动态代理类自动分配到【DB_2020】数据源处理数据。
调用OrderDao 创建订单
OrderDao 创建Order成功
Proxy after method.
达到了与静态代理一样的功能。但是,动态代理实现之后,我们不仅能实现Order的数据源动态路由,还可以实现其他任何类的数据源路由。
代理模式与SpringSpring中代理核心类ProxyFactoryBean,核心方法getObject:我们首先来看看源码:
@Override
@Nullable
public Object getObject() throws BeansException {
initializeAdvisorChain();
if (isSingleton()) {
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
return newPrototypeInstance();
}
}
在getObject()方法中,主要调用getSingletonInstance()和newPrototypeInstance(),在Spring的配置中,如果不做任何设置,那么Spring代理生成的Bean都是单例对象。如果修改scope则每次创建一个新的原型对象. Spring利用动态代理实现AOP有两个非常重要的类,一个是JdkDynamicAopProxy类和CglibAopProxy类,来看一下类图: Spring中的代理选择原则 1、当Bean有实现接口时,Spring就会用JDK的动态代理 2、当Bean没有实现接口时,Spring选择CGLib。 3、Spring可以通过配置强制使用CGLib,只需在Spring的配置文件中加入如下代码:
关与JDK的代理和CGLib我会另外整理单独记录,到时候会及时补上,还请见谅。
代理模式优缺点: 优点: 1、代理模式能将代理对象与真实被调用的目标对象分离。 2、一定程度上降低了系统的耦合度,扩展性好。 3、可以起到保护目标对象的作用。 4、可以对目标对象的功能增强。 缺点: 1、代理模式会造成系统设计中类的数量增加。 2、在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。 3、增加了系统的复杂度。
望各位看过的伙伴们如果发现了问题能够及时批评指正,在此感谢。