try{
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
returnmethod.invoke(subject,args);
}catch(Exceptione){
times++;
if(times>=RetryConstant.MAX_TIMES){
thrownewRuntimeException(e);
}
}
}
returnnull;
}
/**
*获取动态代理
*
*@paramrealSubject代理对象
*/
publicstaticObjectgetProxy(ObjectrealSubject){
//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandlerhandler=newDynamicProxy(realSubject);
returnProxy.newProxyInstance(handler.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),handler);
}
}
•测试代码
@Test
publicvoidfailUserServiceTest(){
UserServicerealService=newUserServiceImpl();
UserServiceproxyService=(UserService)DynamicProxy.getProxy(realService);
Useruser=proxyService.queryUser(newQueryUserCondition());
LOGGER.info("failUserServiceTest:
"+user);
}
@Test
publicvoidroleServiceTest(){
RoleServicerealService=newRoleServiceImpl();
RoleServiceproxyService=(RoleService)DynamicProxy.getProxy(realService);
booleanhasPrivilege=proxyService.hasPrivilege(newUser());
LOGGER.info("roleServiceTest:
"+hasPrivilege);
}
V1.3动态代理模式增强
对话
项目经理:
小明,你动态代理的方式是挺会偷懒的,可是我们有的类没有接口。
这个问题你要解决一下。
小明:
好的。
(谁?
写服务竟然不定义接口)
•ResourceServiceImpl.java
publicclassResourceServiceImpl{
/**
*校验资源信息
*@paramuser入参
*@return是否校验通过
*/
publicbooleancheckResource(Useruser){
OutServiceoutService=newAlwaysFailOutServiceImpl();
outService.remoteCall();
returntrue;
}
}
字节码技术
小明看了下网上的资料,解决的办法还是有的。
•CGLIB
CGLIB是一个功能强大、高性能和高质量的代码生成库,用于扩展JAVA类并在运行时实现接口。
•javassist
javassist(Java编程助手)使Java字节码操作变得简单。
它是Java中编辑字节码的类库;它允许Java程序在运行时定义新类,并在JVM加载类文件时修改类文件。
与其他类似的字节码编辑器不同,Javassist提供了两个级别的API:
源级和字节码级。
如果用户使用源代码级API,他们可以编辑类文件,而不需要了解Java字节码的规范。
整个API只使用Java语言的词汇表进行设计。
您甚至可以以源文本的形式指定插入的字节码;Javassist动态编译它。
另一方面,字节码级API允许用户直接编辑类文件作为其他编辑器。
•ASM
ASM是一个通用的Java字节码操作和分析框架。
它可以用来修改现有的类或动态地生成类,直接以二进制形式。
ASM提供了一些通用的字节码转换和分析算法,可以从这些算法中构建自定义复杂的转换和代码分析工具。
ASM提供与其他Java字节码框架类似的功能,但主要关注性能。
因为它的设计和实现都尽可能地小和快,所以非常适合在动态系统中使用(当然也可以以静态的方式使用,例如在编译器中)。
实现
小明看了下,就选择使用CGLIB。
•CglibProxy.java
publicclassCglibProxyimplementsMethodInterceptor{
@Override
publicObjectintercept(Objecto,Methodmethod,Object[]objects,MethodProxymethodProxy)throwsThrowable{
inttimes=0;
while(timestry{
//通过代理子类调用父类的方法
returnmethodProxy.invokeSuper(o,objects);
}catch(Exceptione){
times++;
if(times>=RetryConstant.MAX_TIMES){
thrownewRuntimeException(e);
}
}
}
returnnull;
}
/**
*获取代理类
*@paramclazz类信息
*@return代理类结果
*/
publicObjectgetProxy(Classclazz){
Enhancerenhancer=newEnhancer();
//目标对象类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术创建目标对象类的子类实例作为代理
returnenhancer.create();
}
}
•测试
@Test
publicvoidfailUserServiceTest(){
UserServiceproxyService=(UserService)newCglibProxy().getProxy(UserServiceImpl.class);
Useruser=proxyService.queryUser(newQueryUserCondition());
LOGGER.info("failUserServiceTest:
"+user);
}
@Test
publicvoidresourceServiceTest(){
ResourceServiceImplproxyService=(ResourceServiceImpl)newCglibProxy().getProxy(ResourceServiceImpl.class);
booleanresult=proxyService.checkResource(newUser());
LOGGER.info("resourceServiceTest:
"+result);
}
V2.0AOP实现
对话
项目经理:
小明啊,最近我在想一个问题。
不同的服务,重试的时候次数应该是不同的。
因为服务对稳定性的要求各不相同啊。
小明:
好的。
(心想,重试都搞了一周了,今天都周五了。
)
下班之前,小明一直在想这个问题。
刚好周末,花点时间写个重试小工具吧。
设计思路
•技术支持
spring
java注解
•注解定义
注解可在方法上使用,定义需要重试的次数
•注解解析
拦截指定需要重试的方法,解析对应的重试次数,然后进行对应次数的重试。
实现
•Retryable.java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceRetryable{
/**
*Exceptiontypethatareretryable.
*@returnexceptiontypetoretry
*/
Class
extendsThrowable>value()defaultRuntimeException.class;
/**
*包含第一次失败
*@returnthemaximumnumberofattempts(includingthefirstfailure),defaultsto3
*/
intmaxAttempts()default3;
}
•RetryAspect.java
@Aspect
@Component
publicclassRetryAspect{
@Pointcut("execution(public*com.github.houbb.retry.aop..*.*(..))&&"+
"@annotation(com.github.houbb.retry.aop.annotation.Retryable)")
publicvoidmyPointcut(){
}
@Around("myPointcut()")
publicObjectaround(ProceedingJoinPointpoint)throwsThrowable{
Methodmethod=getCurrentMethod(point);
Retryableretryable=method.getAnnotation(Retryable.class);
//1.最大次数判断
intmaxAttempts=retryable.maxAttempts();
if(maxAttempts<=1){
returnpoint.proceed();
}
//2.异常处理
inttimes=0;
finalClass
extendsThrowable>exceptionClass=retryable.value();
while(timestry{
returnpoint.proceed();
}catch(Throwablee){
times++;
//超过最大重试次数or不属于当前处理异常
if(times>=maxAttempts||
!
e.getClass().isAssignableFrom(exceptionClass)){
thrownewThrowable(e);
}
}
}
returnnull;
}
privateMethodgetCurrentMethod(ProceedingJoinPointpoint){
try{
Signaturesig=point.getSignature();
MethodSignaturemsig=(MethodSignature)sig;
Objecttarget=point.getTarget();
returntarget.getClass().getMethod(msig.getName(),msig.getParameterTypes());
}catch(NoSuchMethodExceptione){
thrownewRuntimeException(e);
}
}
}
方法的使用
•fiveTimes()
当前方法一共重试5次。
重试条件:
服务抛出AopRuntimeExption
@Override
@Retryable(maxAttempts=5,value=AopRuntimeExption.class)
publicvoidfiveTimes(){
LOGGER.info("fiveTimescalled!
");
thrownewAopRuntimeExption();
}
•测试日志
2018-08-0815:
49:
33.814INFO[main]com.github.houbb.retry.aop.service.impl.UserServiceImpl:
66-fiveTimescalled!
2018-08-0815:
49:
33.815INFO[main]com.github.houbb.retry.aop.service.impl.UserServiceImpl:
66-fiveTimescalled!
2018-08-0815:
49:
33.815INFO[main]com.github.houbb.retry.aop.service.impl.UserServiceImpl:
66-fiveTimescalled!
2018-08-0815:
49:
33.815INFO[main]com.github.houbb.retry.aop.service.impl.UserServiceImpl:
66-fiveTimescalled!
2018-08-0815:
49:
33.815INFO[main]com.github.houbb.retry.aop.service.impl.UserServiceImpl:
66-fiveTimescalled!
java.lang.reflect.UndeclaredThrowableException
...
V3.0spring-retry版本
对话
周一来到公司,项目经理又和小明谈了起来。
项目经理:
重试次数是满足了,但是重试其实应该讲究策略。
比如调用外部,第一次失败,可以等待5S在次调用,如果又失败了,可以等待10S再调用。
。
。
小明:
了解。
思考
可是今天周一,还有其他很多事情要做。
小明在想,没时间写这个呀。
看看网上有没有现成的。
spring-retry
SpringRetry为Spring应用程序提供了声明性重试支持。
它用于Spring批处理、Spring集成、ApacheHadoop(等等)的Spring。
在分布式系统中,为了保证数据分布式事务的强一致性,大家在调用RPC接口或者发送MQ时,针对可能会出现网络抖动请求超时情况采取一下重试操作。
大家用的最多的重试方式就是MQ了,但是如果你的项目中没有引入MQ,那就不方便了。
还有一种方式,是开发者自己编写重试机制,但是大多不够优雅。
注解式使用
•RemoteService.java
重试条件:
遇到RuntimeException
重试次数:
3
重试策略:
重试的时候等待5S,后面时间依次变为原来的2倍数。
熔断机制:
全部重试失败,则调用recover()方法。
@Service
publicclassRemoteService{
privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(RemoteService.class);
/**
*调用方法
*/
@Retryable(value=RuntimeException.class,
maxAttempts=3,
backoff=@Backoff(delay=5000L,multiplier=2))
publicvoidcall(){
LOGGER.info("Callsomething...");
thrownewRuntimeException("RPC调用异常");
}
/**
*recove