代理和经销商合作模式 (物业公司加盟代理模式)

代理模式

代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

通俗点说,就是一个中介,比如有一个广州人,是个本地人,有两套房,他要租出去收租,但是除了收租,他还要去找租客,带租客看房,还要准备租房合同,核算水电费等等,很麻烦。这个本地人他也不想这么折腾,他只想完成他的核心业务(收钱),其他杂七杂八的事情就不想管,但是总要有人去做,那就找租房中介,也就是二手房东。二手房东就代理这个广州本地人把房子租给租客。这个道理就是这么简单。

他们这些在广州有房子的本地人都可以找中介公司去代理租房是一样的。因为很多广州本地人都有这个需求,干脆就搞一个中介公司来专门去做租房子的事情。

代理模式,运用在编程里,也是这个道理,有一些非核心业务的代码,在很多地方都需要用到的逻辑,可以交给代理对象完成,程序员只需要关心核心业务的逻辑即可。

接下来就分别介绍三种代理模式的实现方式:

静态代理

假设原来有一个接口UserService,controller层调用userService的getAllUser()方法。如下所示:

publicinterfaceUserService{
/**
*获取所有用户信息
*
*@returnList
*@authorYehongzhi
*@date2020/4/12
*/
List<User>getAllUser()throwsException;
}
@RestController
@RequestMapping("/xiaoniu")
publicclassUserController{

@Resource(name="userService")
privateUserServiceuserService;

@RequestMapping("/getAllUser")
publicList<User>getAllUser()throwsException{
returnuserService.getAllUser();
}
}

如果用静态代理实现记录日志信息,怎么记录呢?

首先创建一个代理类UserServiceProxy,实现UserService接口,然后在UserServiceProxy里面创建一个成员变量userService,再写一个有参构造器来初始化userService。代码如下:

publicclassUserServiceProxyimplementsUserService{

privateUserServiceuserService;

publicUserServiceProxy(UserServiceuserService){
this.userService=userService;
}

@Override
publicList<User>getAllUser()throwsException{
System.out.println("记录日志:执行getAllUser()方法前");
List<User>userList=userService.getAllUser();
System.out.println(userList);
System.out.println("记录日志:执行getAllUser()方法后");
returnuserList;
}
}

所以在controller层调用的方式就要改一下,是用代理类UserServiceProxy调用getAllUser()方法。如下:

@RestController
@RequestMapping("/xiaoniu")
publicclassUserController{

@Resource(name="userService")
privateUserServiceuserService;

@RequestMapping("/getAllUser")
publicList<User>getAllUser()throwsException{
returnnewUserServiceProxy(userService).getAllUser();
}
}

然后启动项目,调用一下接口,就可以看到控制台打印如下日志:

/*
记录日志:执行getAllUser()方法前
[User{id=1,name='大司马',age=36,job='厨师'},User{id=2,name='朴老师',age=36,job='主播'},User{id=3,name='王刚',age=30,job='厨师'},User{id=4,name='大sao',age=32,job='美食up主'},User{id=5,name='姚大秋',age=35,job='主持人'}]
记录日志:执行getAllUser()方法后
*/

这就是静态代理的实现思路,很简单。但是一般我们肯定是不用这种方式。因为这种方式太笨了,很容易就可以看出几个缺点。

1.要实现接口,也就是目标的方法要定义一个接口方法,实际上是运用了java多态的特性

2.第一点还不是致命的,因为JDK动态代理也是必须要定义接口;致命的是每一个你想代理的接口你都要去创建一个代理类去实现,假设有很多要代理的接口,那就创建很多代理类,这样显得很臃肿

假设还是不理解为什么要动态代理,不妨我们再多加一个支付接口PayService,这个支付接口我们也要加上日志记录。

用静态代理怎么做?很简单呀,再创建一个PayServiceProxy类不就完了吗,如果还有OrderService(订单),

WarehouseService(仓库)等等。那就要创建很多XXXServiceProxy类。如果使用动态代理,就没必要创建这么多代理类,创建一个代理类就够了!

动态代理就是为了解决静态代理的这个缺点产生的。

JDK动态代理

JDK本身就带有动态代理,必须要满足一个条件,就是要有接口。原理其实和静态代理是一样的,也是用代理类去实现接口,但是代理类不是一开始就写好的,而是在程序运行时通过反射创建字节码文件然后加载到JVM。也就是动态生成的代理类对象。

下面就是用JDK动态代理实现代理模式。

publicclassLogRecordProxy<T>implementsInvocationHandler{

privateTtarget;

publicLogRecordProxy(Tt){
this.target=t;
}

@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
System.out.println("记录日志:执行"+method.getName()+"方法前");
Objectresult=method.invoke(target,args);
System.out.println(result);
System.out.println("记录日志:执行"+method.getName()+"方法后");
returnresult;
}

/**
*获取代理对象的方法
**/
@SuppressWarnings("unchecked")
public<T>TgetProxy()throwsException{
return(T)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
}

在controller层,就要改成这样。

@RestController
@RequestMapping("/xiaoniu")
publicclassUserController{

@Resource
privateUserServiceuserService;

@RequestMapping("/getAllUser")
publicList<User>getAllUser()throwsException{
//获取代理对象
UserServiceuserServiceProxy=newLogRecordProxy<>(userService).getProxy();
returnuserServiceProxy.getAllUser();
}
}

假设有一个PayService也要做日志记录,就可以直接使用。

@Resource(name="payService")
privatePayServicepayService;

@RequestMapping("/pay")
publicStringpay(@RequestParam(name="channel")Stringchannel,
@RequestParam(name="amount")Stringamount
)throwsException{
//获取代理对象,实际上就在构造器上改一下传入的参数即可
PayServicepayServiceProxy=newLogRecordProxy<>(payService).getProxy();
returnpayServiceProxy.pay(channel,amount);
}

很多文章给的例子都不带泛型,也可以,就是获取的代理对象需要强转成对应的接口类。

注意:这里一定要用接口接收代理对象,不能用实现类!

因为返回的对象已经不是实现类的对象,而是和实现类有共同的接口类的代理类对象,所以当然只能用接口类去接收。

这也是为什么一再强调要面向接口编程的原因,因为面向接口编程可以做更多的扩展。假设是面向实现类去编程,那就不能用JDK动态代理去扩展了!

CGLB动态代理

那如果有些场景真的没有接口呢,我们怎么运用代理模式?

首先引入maven配置

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>

然后创建一个方法拦截器LogRecordInterceptor,要实现MethodInterceptor类,如下:

publicclassLogRecordInterceptorimplementsMethodInterceptor{

privateObjecttarget;

publicLogRecordInterceptor(Objecttarget){
this.target=target;
}

@Override
publicObjectintercept(Objectobject,Methodmethod,Object[]args,MethodProxymethodProxy)throwsThrowable{
System.out.println("记录日志:执行"+method.getName()+"方法前,参数:"+Arrays.toString(args));
Objectresult=method.invoke(target,args);
System.out.println(result);
System.out.println("记录日志:执行"+method.getName()+"方法后,参数:"+Arrays.toString(args));
returnresult;
}
}

然后再创建一个工厂类InterceptorFactory,用于创建代理对象。

publicclassInterceptorFactory{

@SuppressWarnings("unchecked")
publicstatic<T>TgetInterceptor(Class<T>clazz,MethodInterceptormethodInterceptor){
Enhancerenhancer=newEnhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(methodInterceptor);
return(T)enhancer.create();
}
}

接着我们就可以创建一个没有接口的类,我这里就创建一个数学工具类进行测试

publicclassMathUtil{
/**
*获取一个数的平方
**/
publicStringgetSquare(intnum){
returnString.valueOf(num*num);
}
}

然后在controller层定义一个接口来测试

@RequestMapping("/getSquare")
publicStringgetSquare(@RequestParam(name="num")Integernum)throwsException{
MathUtilmathUtil=InterceptorFactory.getInterceptor(MathUtil.class,newLogRecordInterceptor(newMathUtil()));
returnmathUtil.getSquare(num);
}

用浏览器或者POSTMAN工具调用接口,就可以在控制台看到以下输出:

/*
记录日志:执行getSquare方法前,参数:[2]
4
记录日志:执行getSquare方法后,参数:[2]
*/

这样就实现没有定义接口也可以实现动态代理!

实际上,定义接口的也可以用这种方法来进行扩展,比如上面的userService接口

@RestController
@RequestMapping("/xiaoniu")
publicclassUserController{

@Resource
privateUserServiceuserService;

@RequestMapping("/getAllUser")
publicList<User>getAllUser()throwsException{
UserServiceImpluserServiceProxy=InterceptorFactory
.getInterceptor(UserServiceImpl.class,
newLogRecordInterceptor(userService));
returnuserServiceProxy.getAllUser();
}
}

调用接口我们在控制台也是可以看到以下输出日志:

/*
记录日志:执行getAllUser方法前,参数:[]
[User{id=1,name='大司马',age=36,job='厨师'},User{id=2,name='朴老师',age=36,job='主播'},User{id=3,name='王刚',age=30,job='厨师'},User{id=4,name='大sao',age=32,job='美食up主'},User{id=5,name='姚大秋',age=35,job='主持人'}]
记录日志:执行getAllUser方法后,参数:[]
*/

最后说几句

通过以上的学习我们学会了三种代理模式的实现方式,分别是:

1.静态代理

2.JDK动态代理

3.CGLB动态代理

实际上真正运用在框架中,一般来说都是JDK动态代理和CGLB动态代理结合一起使用的。比如Spring的AOP,还有Mybatis框架。所以学习代理模式对于看框架源码是有很大帮助的。

更多的设计模式分享就关注我吧

我是一个在互联网荒野求生的程序员。我们下期再见!