动态代理和责任链设计模式适用范围广,在Spring和MyBatis有着重要的应用,比如SpringAOP、Mybatis的插件技术,想要搞懂当中的技术原理必须掌握上面两个设计模式。
代理模式可以理解为您要操作一个对象,但是要经过这个对象的“代理”对象去操作。就好似你在一家软件公司做开发,客户发现程序有Bug,会找到商务对接人说,最后商务的同事再找到你去解决问题。“商务”是代理对象,“你”是真实对象。代理模式分为静态代理和动态代理,其作用是可以在真实对象访问之前或者之后加入自定义的逻辑,又或者根据自定义规则来控制是否使用真实对象。
静态代理是真实对象与代理对象(Proxy)实现相同的接口,代理对象包含真实对象的引用,客户端通过代理对象去访问真实对象,代理对象可以在真实对象访问之前或之后执行其他操作。假设要你设计一个对外开放的商品库存信息查询接口,并且要限制调用方在一天时间内的调用次数。用代理模式代理库存接口,首先在库存查询前检查用户是否有权限访问,然后在查询后要记录用户查询日志,以便根据查询次数,判断调用上限。

动态代理是在程序运行时,通过反射机制创建代理对象,实现动态代理方法。动态代理相比于静态代理的好处,是代理对象不用实现真实对象的接口,这样能代理更多方法。因为静态代理是一个接口对应一个类型,如果接口添加新方法,则所有代理类都要实现此方法,所以动态代理脱离了接口实现,一个代理类就能代理更多方法。一些公共代码逻辑也就可以在多个代理方法里复用,例如:数据库事务开启、提交、回滚,这些公共代码都分别是在真实方法调用的前后出现,而动态代理会帮我们把功能代码织入到方法里。在Java中最常用动态代理有两种,一种是JDK动态代理,这是JDK自带的功能;另一种是CGLIB,由第三方提供的一个技术,Spring是用了JDK代理和CGLIB两种,两者的区别是,JDK代理要提供接口作为于代理参数才能使用,而CGLIB不需要提供接口,只要一个非抽象类就能代理,适用于一些不能提供接口的场景。
1. JDK动态代理
(1)定义真实对象接口,因为JDK动态代理要借助接口才能代理对象
public interface SayService {
void sayHello();
}
public class SayServiceImpl implements SayService {
@override
public void sayHello() {
System.out.println("Hello Friend");
}
}
(2)创建代理类,实现java.lang.reflect.InvocationHandler接口
public class JdkProxyExample implements InvocationHandler {
// 真实对象
private Object target = null;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理前");
System.out.println("调用真实对象");
Object r = method.invoke(target, args);
System.out.println("代理后");
return r;
}
}
Proxy.newProxyInstance方法的作用是创建代理对象,并建立代理对象与真实对象的关系。包含3个参数
第1个是类加载器,用于把Java字节码转换成Class类实例对象,这里用了target对象所属的类加载器
第2个是生成的动态代理对象需要挂载到哪些接口下,上面是取了真实对象的接口
第3个是实现InvocationHandler接口的代理类,this表示当前对象
InvocationHandler接口的invoke方法实现代理逻辑,invoke其中参数含义如下
proxy:代理对象,就是bind方法生成的对象
method:当前调用方法,method.invoke(target, args)调用真实对象方法
args:当前调用方法参数
(3)测试JDK动态代理
public void testJdk() {
JdkProxyExample jdkProxy = new JdkProxyExample();
SayService proxy = (SayService) jdkProxy.bind(new SayServiceImpl());
proxy.sayHello();
}
/*
代理前
调用真实对象
Hello Friend
代理后
*/
2. CGLIB动态代理
JDK动态代理要提供接口才能代理,但在一些不能提供接口的场景下,CGLIB动态代理技术不需要提供接口,只要一个非抽象类就能动态代理。新建类实现MethodInterceptor接口(spring框架的cglib包),使用Enhacer创建代理对象。代码实现如下:
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyExample implements MethodInterceptor {
public Object bind(Class classz) {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(this);
enhancer.setSuperclass(classz);
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("调用真实对象前");
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("调用真实对象后");
return result;
}
}
@Test
public void testCglib() {
CglibProxyExample cglibProxy = new CglibProxyExample();
TestService proxy = (TestService)cglibProxy.bind(TestService.class);
proxy.sayHello();
}
/*
调用真实对象前
Hi hello
调用真实对象后
*/
3. 拦截器
由于动态代理一般不好理解,通常会设计一个拦截器接口提供给开发者使用,这样只需要知道拦截器接口方法、含义和作用即可,无须知道动态代理是怎么实现。拦截器接口设计如下:
public interface Interceptor {
boolean before(Object target);
Object around(Object target, Method method, Object[] args);
void after(Object target);
void afterThrowing(Object target);
void afterReturning(Object target);
}
假定拦截器使用规则是:在调用真实方法前,先访问before方法,如果返回true,执行真实对象方法,否则执行around方法代替真实方法的调用。after方法在真实方法或around调用后执行,如果真实方法或around调用异常,执行afterThrowing方法,否则执行afterReturning方法。
定义了以上规则,开发者只要知道拦截器怎样使用,不需要知道动态代理怎么实现。
下面代码演示如何使用上面定义的拦截器
public class SayServiceInterceptor implements Interceptor {
@Override
public boolean before(Object target) {
System.out.println("代理前执行before方法");
// 不执行真实对象的方法
return false;
}
@Override
public Object around(Object target, Method method, Object[] args) {
System.out.println("真实对象方法被替换,执行around");
return null;
}
@Override
public void after(Object target) {
System.out.println("代理后执行after方法");
}
@Override
public void afterThrowing(Object target) {
System.out.println("真实对象方法调用异常,执行afterThrowing方法");
}
@Override
public void afterReturning(Object target) {
System.out.println("最后执行afterReturning方法");
}
}
public void testJdk() {
SayService say = new SayServiceImpl();
SayServiceInterceptor interceptor = new SayServiceInterceptor();
SayService proxy = (SayService) JdkProxyExample.bind(say, interceptor);
proxy.sayHello();
}
/**
代理前执行before方法
真实对象方法被替换,执行around
代理后执行after方法
最后执行afterReturning方法
*/
上面规则用动态代理实现代码如下:
public class JdkProxyExample implements InvocationHandler {
// 真实对象
private Object target = null;
// 拦截器
private Interceptor interceptor;
public JdkProxyExample(Object target, Interceptor interceptor) {
this.target = target;
this.interceptor = interceptor;
}
public static Object bind(Object target, Interceptor interceptor) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new JdkProxyExample(target, interceptor));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(interceptor == null) {
return method.invoke(target, args);
}
Object result = null;
boolean exceptionFlag = false;
try {
if (interceptor.before(target)) {
result = method.invoke(target, args);
}else {
result = interceptor.around(target, method, args);
}
}catch (Exception e) {
exceptionFlag = true;
} finally {
interceptor.after(target);
}
if(exceptionFlag) {
interceptor.afterThrowing(target);
}else {
interceptor.afterReturning(target);
}
return result;
}
}
设计拦截器能简化了动态代理的使用方法,使程序更简单。在实际使用场景中拦截器实现后,要在开发者的程序进行xml配置,或者在实现类添加注解标识等方式,来查找到拦截器实现类,然后反射创建并加载到程序里。SpringAOP也是通过@Aspect注解创建切面(拦截器),@execution定义连接点拦截方法。
4. 责任链模式
设计拦截器去代替动态代理,然后将拦截器的接口提供给开发者用,从而简化开发者的开发难度,但是拦截器可能会有多个,举个例子,您要请假一周,然后在OA上提交请假申请单,要经过项目经理、部门经理、人事等多个角色的审批,每个角色都会对申请单拦截、修改、审批等。如果把请假申请单看做一个对象,则它会经过三个拦截器的拦截处理。当一个对象在一条链上被多个拦截器拦截处理时,我们把这样的设计模式称为责任链模式。前一个拦截器的返回结果会作用于后一个拦截器,代码上的实现是用后一个拦截器去代理了前一个拦截器的方法,以此类推,层层代理。最终结果如下图:
代码实现如下:
@Test
public void testInterceptor() {
SayService target = new SayServiceImpl();
SayService proxy1 = (SayService)JdkProxyExample.bind(target, new SayServiceInterceptor("proxy1"));
SayService proxy2 = (SayService)JdkProxyExample.bind(proxy1, new SayServiceInterceptor("proxy2"));
SayService proxy3 = (SayService)JdkProxyExample.bind(proxy2, new SayServiceInterceptor("proxy3"));
proxy1.sayHello();
}
/**
proxy3:代理前执行before方法
proxy2:代理前执行before方法
proxy1:代理前执行before方法
Hello Friends
proxy1:代理后执行after方法
proxy2:代理后执行after方法
proxy3:代理后执行after方法
*/
before方法执行顺序是从最后一个拦截器到第一个拦截器,而after方法是从第一个拦截器到最后一个。责任链模式的优点在于我们可以在传递链中加上新的拦截器,增加拦截逻辑,但缺点是每增加一层代理反射,程序性能越差。
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl
有没有办法在Ruby中动态创建数组?例如,假设我想遍历用户输入的书籍数组:books=gets.chomp用户输入:"TheGreatGatsby,CrimeandPunishment,Dracula,Fahrenheit451,PrideandPrejudice,SenseandSensibility,Slaughterhouse-Five,TheAdventuresofHuckleberryFinn"我把它变成一个数组:books_array=books.split(",")现在,对于用户输入的每一本书,我想用Ruby创建一个数组。伪代码来做到这一点:x=0books_array.
我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO
我是Ruby的新手。我试过查看在线文档,但没有找到任何有效的方法。我想在以下HTTP请求botget_response()和get()中包含一个用户代理。有人可以指出我正确的方向吗?#PreliminarycheckthatProggitisupcheck=Net::HTTP.get_response(URI.parse(proggit_url))ifcheck.code!="200"puts"ErrorcontactingProggit"returnend#Attempttogetthejsonresponse=Net::HTTP.get(URI.parse(proggit_url)
有人知道如何将capybarapoltergeist的用户代理覆盖到移动用户代理以进行测试吗?我发现了一些有关为seleniumwebdriver配置它的信息:http://blog.plataformatec.com.br/2011/03/configuring-user-agents-with-capybara-selenium-webdriver/这在capybara闹鬼中怎么可能? 最佳答案 请参阅poltergeistgithub页面上的链接:https://github.com/teampoltergeist/polte
我正在使用Ruby/Mechanize编写一个“自动填写表格”应用程序。它几乎可以工作。我可以使用精彩CharlesWeb代理以查看服务器和我的Firefox浏览器之间的交换。现在我想使用Charles查看服务器和我的应用程序之间的交换。Charles在端口8888上代理。假设服务器位于https://my.host.com。.一件不起作用的事情是:@agent||=Mechanize.newdo|agent|agent.set_proxy("my.host.com",8888)end这会导致Net::HTTP::Persistent::Error:...lib/net/http/pe