之前介绍过一篇关于AOP的文章,主要是介绍其概念、基本用法以及常见的应用场景,但感觉没有讲解透彻,这篇文章将使用一个常见的例子一步一步的演进为什么会需要用到AOP以及AOP的实现原理。涉及到的技术包括如下:
代理模式
动态代理
AOP
Annotation的使用
实际场景
在微服务调用过程中,经常会出现异常,或者是服务本身有问题,或者是网络瞬间抖动导致调用失败,当遇到这种情况的时候肯定需要重试机制,微服务RPC调用框架都已经实现了这个功能,比如SpringCloud中的Feign只需要通过简单的配置就能实现重现机制,如下:
service:
ribbon:
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 2
ConnectTimeout: 5000
ReadTimeout: 2000OkToRetryOnAllOperations: true
这篇文章不是介绍如何实现重试机制,只是把它当作一个具体场景,来一步步演进来实现这个功能,最后达到理解AOP技术。
最直接方式
拿到这个需求,最简单粗暴的方法就很简单,在调用另一个服务的地方加上重试,重试指定次数仍然失败则抛出异常。这个代码就比较简单,其中outerService方法是模拟外部服务的异常,如下:
上面的代码很简单,但如果项目中有很多的微服务,每个调用其他服务的代码都这么写,功能虽然没啥问题,但可读性会很差。因此,自然会想到如何改进这段代码,了解过代理模式的同学应该会想到使用代理来实现。
应用代理模式
首先回忆一下设计模式中的代理模式,代理模式的定义如下:
代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。客户通过中介买房,都是完成买房这个任务,在编程中可以理解为买房是一个接口,中介和客户都需要实现这个接口,中介在买房的任务中需要做很多其他的事情,比如约卖家,准备合同等,当需要客户参与时,就需要喊上客户来完成,在编程中就理解为在中介买房的过程中,需要调用客户的买房接口。类图如下:
回到我们的场景中,我们可以实现一个代理类,这个代理类来实现重试机制,当在真正调用服务的地方,再调用具体的服务来实现,是不是完全满足代理模式?重构代码如下:
public interface HelloService {
void sayHello();
}
@Slf4j
@Service("HelloService")
public class HelloServiceImpl implements HelloService {
private static final int MAX_TRIES_COUNT = 5;
private void outerService(){
System.out.println("call outer service.....");
throw new RuntimeException("Out service throw exception");
}
@Override
public void sayHello() {
outerService();
}
}
@Service("HelloServiceProxy")
public class HelloServiceProxyImpl implements HelloService {
private static final int MAX_TRIES_COUNT = 5;
private HelloService helloService;
public HelloServiceProxyImpl(HelloService helloService){
helloService = helloService;
}
@Override
public void sayHello() {
int count = 0;
while (count < MAX_TRIES_COUNT) {
try {
helloService.sayHello();
break;
} catch (Exception e) {
count++;
if(count >= MAX_TRIES_COUNT) {
throw new RuntimeException(e);
}
}
}
}
}
使用代理模式来实现这个功能,代码可读性和可维护性就大大提高,不需要在每个服务调用的地方都增加重试逻辑,交给代理来来完成即可。但是,如果项目中有很多的服务,需要为每个服务都实现一个代理类来实现原有接口,这个开发工作量也是相当大的而且很冗余,因此熟悉动态代理的同学应该想到可以使用JAVA的动态代理功能来完成,使用反射技术来生成动态代理类。
基于接口的动态代理
Java中动态代理的实现,关键就是这两个东西:Proxy、InvocationHandler。InvocationHandler接口Invoke参数如下:
/**
*
* @param proxy, 代理类实例
* @param method,调用的方法
* @param args,调用方法参数
* @return 方法的返回值*/
public Object invoke(Object proxy, Method method, Object[] args);
另外一个创建动态代理类的方法如下:
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
因此,使用基于接口的动态代理类也可以很容易实现此功能,代码如下:
public class HelloServiceImpl implements HelloService {
private void outerService(){
System.out.println("call outer service.....");
throw new RuntimeException("Out service throw exception");
}
@Override
public void sayHello() {
outerService();
}
}
public class ServiceDynamicProxy implements InvocationHandler {
private static final int MAX_TRIES_COUNT = 5;
private final Object service;
public ServiceDynamicProxy(Object service) {
this.service = service;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
int count = 0;
while (count < MAX_TRIES_COUNT) {
try {
return method.invoke(service, args);
} catch (Exception e) {
count++;
if (count >= MAX_TRIES_COUNT) {
throw new RuntimeException(e);
}
}
}
return null;
}
/**
* 获取动态代理
*
* @param realService 代理对象 */
public static Object getProxy(Object realService) {
//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new ServiceDynamicProxy(realService);
return Proxy.newProxyInstance(handler.getClass().getClassLoader(),
realService.getClass().getInterfaces(), handler);
}
}
@Test
void testDynamicProxy(){
HelloService helloService = new HelloServiceImpl();
HelloService proxyService = (HelloService)
ServiceDynamicProxy.getProxy(helloService);
proxyService.sayHello();
}
上面实现对于面向接口的动态代理是没问题,但是如果代理类没有接口,这种方式不能成功。
CGLIB方式生成动态子类
使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。使用CGLIB主要依赖MethodInterceptor和Enhancer两个类,
Object intercept(Object service, Method method, Object[] args, MethodProxy methodProxy)
使用CGLIB方式实现此功能代码如下:
public class HelloServiceImpl {
private void outerService(){
System.out.println("call outer service.....");
throw new RuntimeException("Out service throw exception");
}
//@Override
public void sayHello() {
outerService();
}
}
public class ServiceCglibProxy implements MethodInterceptor {
private static final int MAX_TRIES_COUNT = 5;
@Override
public Object intercept(Object service, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
int count = 0;
while (count < MAX_TRIES_COUNT) {
try {
return methodProxy.invokeSuper(service, args);
} catch (Exception e) {
count++;
if (count >= MAX_TRIES_COUNT) {
throw new RuntimeException(e);
}
}
}
return null;
}
public Object getProxy(Class clazz) {
Enhancer enhancer = new Enhancer();
//目标对象类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术创建目标对象类的子类实例作为代理
return enhancer.create();
}
}
@Test
void testCglibProxy(){
HelloServiceImpl helloServiceImpl = (HelloServiceImpl) new
ServiceCglibProxy().getProxy(HelloServiceImpl.class);
helloServiceImpl.sayHello();
}
因此,通过上面两种代理方式,就成功的使用代理模式来完成这个功能的重构,经过重构后,不需要再每个调用的地方都写上一段重复的代码,也不需要为每个服务类创建一个代理类,代码精简了不少。其实这就是AOP的原理,AOP就是基于上面两种代理模式来实现。
终极方案AOP
上面两种代理模式其实就是AOP的实现原理,前一种兄弟模式,spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。后一种父子模式,spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。明白了AOP实现原理,使用AOP就比较简单,定义切入点和Aspect.代码如下:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Retries {
Class<? extends Throwable> value() default RuntimeException.class;
int maxAttempts() default 5;
}
@Slf4j
@Service("HelloService")
public class HelloServiceImpl implements HelloService{
private void outerService(){
System.out.println("call outer service.....");
throw new RuntimeException("Out service throw exception");
}
@Override
@Retries(maxAttempts = 5, value = RuntimeException.class)
public void sayHello() {
outerService();
}
}
@Aspect
@Component
public class RetriesAspect {
@Pointcut("execution(public * com.example.demo.service.impl.*.say*(..)) &&" +
"@annotation(com.example.demo.Retries)")
public void tryPointcut() {
}
private Method getCurrentMethod(ProceedingJoinPoint point) {
try {
Signature sig = point.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
Object target = point.getTarget();
return target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@Around("tryPointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Method method = getCurrentMethod(point);
Retries retries = method.getAnnotation(Retries.class);
int maxAttempts = retries.maxAttempts();
if (maxAttempts <= 1) {
return point.proceed();
}
int count = 0;
final Class<? extends Throwable> exceptionClass = retries.value();
while (count < maxAttempts) {
try {
return point.proceed();
} catch (Throwable e) {
count++;
if (count >= maxAttempts /*|| !e.getClass().isAssignableFrom(exceptionClass)*/) {
throw new Throwable(e);
}
}
}
return null;
}
}
@SpringBootTest
class DemoApplicationTests {
@Autowired
@Qualifier(value = "HelloService")
private HelloService helloService;
@Test
void testAop(){
helloService.sayHello();
}
}
写在最后
通过简单方式,代理方式,动态代理方式,CGlib代理方式到最后的AOP实现方式,能看到整个技术的演变过程,其实也是代码不断重构的过程。AOP的实现原理也是基于动态代理和CGlib两种方式来实现的,把这两种技术理解清楚了,AOP就比较容易理解。
网友评论