AI智能
改变未来

Springboot入门——初学者对AOP代码层实现原理的理解与@AspectJ的实际应用


原理(概念)

AOP,面向切面编程,它是一种约定流程的编程方式。
什么是切面呢?
我们把在业务逻辑中一个对象的方法想象成就是一个切面,切面编程就是在这个方法的前前后后进行一些列的相关操作,比如在方法执行之前设置一个函数编写我们自定义的程序代码,或者在方法执行之后,或者在方法抛错后等等。
初学者此时可能会提出问题,既然是在方法的前后编写自定义的程序,那直接写在方法前面或者后面不就可以了么?可以,但是!这样做大大增加了代码的耦合度。我们的目的,是在方法执行之时,以这个方法执行为触发点,自动的调用我们自定义的前前后后的方法。从而实现解耦。
举个例子,我们在向数据库插入数据时,每次的执行是这样的:连接数据库>调用方法执行sql语句>关闭连接。每次都要重复的去执行连接数据库和关闭连接,这样太繁琐。这个时候,如果对\”调用方法执行sql语句\”这一步进行切面编程,让每次这个方法执行时,在其执行前执行一个我们自定义的方法(连接数据库),在其执行后执行一个我们自定义的方法(关闭连接),这样问题就解决了。

原理(代码)

aop是怎样实现的呢?想像一下糖葫芦。假设我们串糖葫芦时,竹签串的是没有糖衣的纯净版山楂,这时候就相当于我们系统执行了一个对象的某个方法。当我们把山楂淋上糖衣之后,糖衣相当于一个新的对象,我们再用竹签串山楂时,我们其实串的是糖衣,也就是说我们操作的是另一个对象的方法,而在糖衣当中我们又串了山楂。没错,我们就是这样在山楂这个对象方法的前前后后自定义了糖衣,我们操作的是包裹着的糖衣而不是纯净的山楂了,糖衣也就等于是山楂的代理对象了。aop就是这样利用代理对象实现的

HelloService helloService = new HelloServiceImpl();HelloService proxy = (HellowService) ProxyBean.getProxyBean(helloService,new MyInterceptor());proxy.sayHello();//这时执行方法时,就会有我们说的前前后后的自定义事件了。

那ProxyBean类是怎样的呢?

public class ProxyBean implements InvocationHandler{private Object target = null;private Interceptor interceptor = null;public static Object getProxyBean(Object target,Interceptor interceptor){ProxyBean proxyBean = new ProxyBean();proxyBean.target = target; //被代理对象proxyBean.interceptor = interceptor; // 拦截器Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),proxyBean); // 生成代理对象return proxy;}@Overridepublic Object invoke(Object proxy,Method method, Object[] args){boolean exceptionFlag = false;Invocation invocation = new Invocation(target,method,args);Object retObj = null;try{if(this.interceptor.before()){retObj = this.interceptor.around(invocation);}else{retObj = method.invoke(target,args);}}catch(Exception ex){exceptionFlag=true;}this.interceptor.after();if(exceptionFlag){this.interceptor.afterThrowing();}else{this.interceptor.afterReturning();}return null;}}

我们可以看到这里面有两个方法:
getProxyBean(),它的主要作用是调用了生成代理对象的方法:Proxy.newProxyInstance(类加载器,绑定的接口,invocationHandler接口的实现);注意,上面代码中该方法第三个参数传的是本类对象,刚好,本类就实现了InvocationHandler接口。
invoke(),实现了接口InvocationHandler,他做为\”生成代理对象\”方法的第三个参数,它内部都做了什么呢?首先,它new了一个Invocation对象,这个对象是用来干嘛的呢,我们先看一下拦截器接口的代码:

public interface Interceptor{public boolean before();public void after();public Object around(Invoction invocation) throw InvocationTargetException,IllegalAccessException;public void afterReturning();public void afterThrowing();boolean useAround();}

拦截器里面的代码就是我们文章最上面所谓的前前后后的自定义操作。我们注意到里面有个around()方法它的参数就是Invocation对象,再结合上面new这个对象的方法来看,它包含了被代理对象的(target-目标,method-方法,args-参数)信息。
再回过头来继续看invoke()方法,如果我们在传入的拦截器实现类对象中设置了before()方法,那么他接下来就会执行拦截器的around()方法,参数就是刚刚new的那个invocation对象,如果没有,那他会直接利用反射机制,执行被代理对象的方法。接下来,视是否抛错而去执行afterThrowing()和afterReturning()方法。至此,“生成代理对象”方法中的,第三个参数涉及到的逻辑也执行完毕了。
此时,仔细查看,你会发现,如果执行了拦截器的around()方法,并没有实现被代理对象方法的调用啊。这里我们就要再查看一下拦截器接口的实现类具体是怎么实现的,仅仅看around()方法就够了:

@Ovrridepublic Object around(Invocation invocation) throws ...{Object obj = invocation.proceed();return obj;}

找到了,就是这个proceed()方法,它执行了被代理对象的方法。怎么可能呢?不要忘了,invocation对象中包含了target、method、args,足够它反射调用方法了。
至此,aop代码实现就ok了。
很简单,aop的实现需要生成代理对象,而代理对象的生成,需要拦截器接口的实现和InvocationHandler接口的实现作为参数。

Springboot中aop的应用

Springboot中一如既往的以注解方式应用aop:@AspectJ
注意一点,aop是针对方法的,当然,切面可以不单单应用于单个方法。

@Aspectpublic class MyAspect{@Pointcut(\"execution(*com.spr.cn.service.impl.UserServiceImpl.printUser(..))\")public void pointCut(){}@Before(\"pointCut()\")public void before(){...}@After(\"pointCut()\")...@AfterReturning(\"pointCut()\")...@AfterThrowing(\"pointCut()\")...}

其实是可以将@Pointcut中的参数放到其他注释的参数中去的,从而不再需要pointCut()方法,上述写法只是避免了过长参数的重复书写。如:

@Before(\"execution(*com.spr.cn.service.impl.UserServiceImpl.printUser(..))\")public void before(){...}...

参数中是存在正则的:

  • execution 表示拦截正则匹配方法。
  • 星号 表示任意返回类型的方法
  • 包路径
  • 方法名
  • (…) 任意参数
    这里面还可以有其他的表达式,比如&&并且关系,这里不详谈。

在程序应用中,我们要在@Configuration类中装配这个@Aspect修饰的类对象。

@SpringBootApplicationpublic class AppConfig{@Beanpublic MyAspect init(){return new MyAspect();}public static void main...}

在@Around的修饰方法中是包含一个参数的

@Around(\"pointCut()\")public void around(ProceedingPoint jp) throws ... {jp.proceed();//这个方法是在调用被代理对象的方法。}

在其他的修饰中也是可以获取参数的,比如获取被代理对象方法的参数:

@Before(\"pointCut() && args(user))public void before(JoinPoint point, User user){Object[] args = point.getArgs();}

上面代码中,注释中的参数是一种获取方式,JoinPoint参数也是一种获取方式。

如果使用多个@Aspect修饰同一个方法切面时,多个切面的调用顺序是随记的,此时用@Order(1)来并行修饰@Aspect即可自定义切面执行顺序。

@Aspect@Order(1)public class T1{...}@Aspect@Order(2)public class T2{ ... }

AOP的另一种运用方式

为第三方接口补充功能。
我们无法更改第三方接口,利用此方法,我们可以增强接口的方法。

//假设第三方接口的实现类为UserServiceImpl//我们自定义补充的功能public interface UserValidator{ ... }//补充功能接口相应的实现public class UserValidatorImpl implements UserValidator{ ... }
@Aspectpublic class MyAspect{@DeclareParents(value = \"包路径....UserServiceImpl+\",defaultImpl=UserValidatorImpl.class)public UserValidator userValidator;}
//执行时,仅需强转类型,即可使用我们补充的功能UserValidator userValidator = (UserValidator ) userService; //这里的userService可以视为是代理对象,代理对象实现了UserService和UserValidator接口,所以才会可能出现代理对象强转UserValidator。

此处不同与之前的面向切点注释,但他们的原理是相同的。我们在\”生成代理对象\”方法的第二个参数其实是接口数组,也就是说让生成的代理对象挂在了这两个接口之下,所以能够实现强转(注意这里的接口不是说拦截器,是通过target被代理对象生成的接口:target.getClass().getInterfaces())
这个运用方式还是很有用的,我们在补充一些我们无法更改的接口实现类的功能时会用得到。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Springboot入门——初学者对AOP代码层实现原理的理解与@AspectJ的实际应用