Spring AOP的应用

xiaoxiao2022-09-22  40

在实际的应用程序开发中,经常需要在一个服务流程中插入一些与业务逻辑无关的系统服务逻辑(最常见的就是记录日志,权限检查等),如果把所有这些与业务逻辑无关的服务与业务逻辑编织在一起,就会使业务逻辑对象的负担加重,因为它不但要具有业务逻辑的功能,还带有例如记录日志等其他功能,这样就容易产生对象的职责混淆。为了避免对象职责的混淆,我们在设计中就需要将与业务逻辑无关的服务逻辑从业务逻辑中剥离出来,独立设计为一个模块或对象,而在希望需要使用这些对象的时候插入进来,不希望使用的时候去掉即可,这种设计模式就称为AOP。Spring AOP是实现AOP的一种技术。Spring AOP最常用的就是采用XML配置文件的方式。Spring AOP只支持在目标对象的某个方法的前后调用服务代码。下面是使用Spring AOP的几个简单的例子:

1.在目标对象的方法执行之前调用(Before Adivce)首先需要定义目标对象必须实现的接口:

package com.test.spring.aop; public interface IHello { public void sayHello(String name); }

 接着定义一个IHello的实现类:

package com.test.spring.aop; public class HelloSpeaker implements IHello { public void sayHello(String name) { System.out.println("Hello," + name); } }

 现在我们想要在调用对象HelloSpeaker的sayHello()方法前打印一下方法开始执行的日志,一般情况下我们会直接在sayHello()的方法中直接加上日志的代码,这样就把日志和业务逻辑的代码融在了一起,对于要记录大量日志的程序来说,无疑会加重目标对象的负担。我们把打印日志的代码独立出来成为一个专门用于日志记录的对象,这里需要实现Spring的MethodBeforeAdvice接口:

package com.test.spring.aop; import java.lang.reflect.Method; import java.util.logging.Level; import java.util.logging.Logger; import org.springframework.aop.MethodBeforeAdvice; public class LogBeforeAdvice implements MethodBeforeAdvice { private Logger logger = Logger.getLogger(this.getClass().getName()); public void before(Method method, Object[] args, Object target) throws Throwable { logger.log(Level.INFO, "method start..." + method); System.out.println("LogBeforeAdvice: method start... " + method); } }

 在before()方法的实现中,加入了一些记录日志的代码,LogBeforeAdive被设计成一个独立的服务,可以提供给需要这个服务的对象,在下面的配置中,我们把这个服务提供给了前面的HelloSpeaker对象:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="logBeforeAdvice" class="com.test.spring.aop.LogBeforeAdvice"></bean> <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean> <bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="com.test.spring.aop.IHello"></property> <property name="target" ref="helloSpeaker"></property> <property name="interceptorNames"> <list> <value>logBeforeAdvice</value> </list> </property> </bean> </beans>

 在上面的配置文件中,属性target指定了要被服务的目标对象为helloSpeaker,属性interceptorNames指定了使用的服务对象是logBeforeAdvice。这样就建立了目标对象与服务对象之间的联系,下面是测试代码:

package com.test.spring.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class BeforeAdviceTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "com/test/spring/aop/beforeAfterAdvice.xml"); IHello helloProxy = (IHello) context.getBean("helloProxy"); helloProxy.sayHello("world"); } }

 可以看到,测试代码中,取的对象是helloProxy代理对象,而不是helloSpeaker目标对象,取到的代理对象必须转换为IHello接口,这样当调用代理对象的sayHello()方法时,就可以在调用目标对象的sayHello()方法体中的代码前打印日志,实际上流程是先进入LogBeforeAdvice对象执行before()方法,执行完毕后再进入helloSpeaker的sayHello()方法,这一切的流程控制都是有Spring完成的,而我们仅仅需要做的工作就是定义目标对象和服务对象,并编写XML配置文件建立服务对象与目标对象的联系。动手试一下吧,看看执行结果再说。

2.在目标对象的方法执行之后调用(After Advice)这里就不在定义目标对象的接口及实现类了,依然使用前面定义好的目标对象helloSpeaker。要在目标对象的方法执行之后进行一些操作,这里依然是打印日志,Spring AOP要求我们实现AfterReturningAdvice接口:

package com.test.spring.aop; import java.lang.reflect.Method; import java.util.logging.Level; import java.util.logging.Logger; import org.springframework.aop.AfterReturningAdvice; public class LogAfterAdvice implements AfterReturningAdvice { private Logger logger = Logger.getLogger(this.getClass().getName()); public void afterReturning(Object object, Method method, Object[] args, Object target) throws Throwable { logger.log(Level.INFO, "method end... " + method); System.out.println("method end... " + method); } }

 可以看到,这个服务对象的目的是在执行目标对象的方法之后执行afterReturning()方法打印方法执行完毕的日志。下一步就是配置XML文件了,把这个服务提供给前面helloSpeaker目标对象:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="logAfterAdvice" class="com.test.spring.aop.LogAfterAdvice"></bean> <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean> <bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="com.test.spring.aop.IHello"></property> <property name="target" ref="helloSpeaker"></property> <property name="interceptorNames"> <list> <value>logAfterAdvice</value> </list> </property> </bean> </beans>

 可以看到,与前面的配置Before Advice非常相似。测试代码与Before Advice的完全一样,这里不再重复列出。动手试一下,看看是否在执行目标对象的sayHello()方法之后调用了打印结束日志。当然,还可以把Before Advice和After Advice一起提供给目标对象,这样,就可以在执行目标对象的方法的前、后都打印出日志了,配置如下:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="logBeforeAdvice" class="com.test.spring.aop.LogBeforeAdvice"></bean> <bean id="logAfterAdvice" class="com.test.spring.aop.LogAfterAdvice"></bean> <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean> <bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="com.test.spring.aop.IHello"></property> <property name="target" ref="helloSpeaker"></property> <property name="interceptorNames"> <list> <value>logBeforeAdvice</value> <value>logAfterAdvice</value> </list> </property> </bean> </beans>

 可以看到,在interceptorNames属性中,把logBeforeAdvice和logAfterAdvice两个服务对象都提供给了目标对象,实际上我们可以配置任意多个。测试代码依然与前面的一样,不再列出,执行一下,看看结果是否是在执行sayHello()方法之前和之后分别打印了日志。

3.在目标对象的方法执行前后调用(Around Advice)在前面的两种模式中,有一个缺陷,就是无论如何,目标对象的方法总是会被执行,我们不能控制目标对象的方法是否执行,且执行的时机。有时候我们需要自行决定是否要执行目标对象的方法,有时需要在不同的时机才执行目标对象的方法,比如权限检查,当权限检查通过时,才继续执行目标对象的方法,如果权限检查不通过,则进入别的流程中。前面的两种模式是做不到这一点的。在Around Advice这种模式中,我们就可以自行决定是否执行目标对象的方法及其执行的时机了。这种模式需要实现MethodInterceptor接口:

package com.test.spring.aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class LogInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("Log start..."); Object result = null; result = methodInvocation.proceed(); System.out.println("Log end..."); return result; } }

 这个服务实现的方法是invoke()方法,在这个方法中,调用了proceed()方法,当调用了这个方法时,就表示需要执行目标对象的方法,下面是XML配置文件的内容:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="logInterceptor" class="com.test.spring.aop.LogInterceptor"></bean> <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean> <bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="com.test.spring.aop.IHello"></property> <property name="target" ref="helloSpeaker"></property> <property name="interceptorNames"> <list> <value>logInterceptor</value> </list> </property> </bean> </beans>

 如上例中,我们在调用proceed()方法的前后打印了日志,则表示在执行helloSpeaker目标对象的sayHello()方法的前后都打印日志。可以看到,关键就在于proceed()方法的调用,因此,在这种模式中,我们可以在调用proceed()方法的前、后的任何地方插入任何处理逻辑,当然,我们也可以不调用proceed()方法,这样,目标对象的方法就不能执行了。测试代码与前面的完全一样,这里不再列出。对于前面的三种方式,我们可以看出,这几种方式都需要在使用时建立代理对象,然后获取代理对象并把类型转换为目标对象的接口,这需要我们手动创建代理对象,即每一个目标对象都要建立一个相应的代理对象,对于有很多目标对象的应用程序,配置XML文件无疑会是一件痛苦的事。Spring AOP框架中提供了一种自动创建代理的工具。

4.自动代理(BeanNameAutoProxyCreator)要使用Spring AOP的自动代理,需要使用org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator类来完成。这里,目标对象依然使用前面的helloSpeaker,服务对象依然使用前面的LogInterceptor,代码不再列出,下面是配置自动代理的XML文件:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="logInterceptor" class="com.test.spring.aop.LogInterceptor"></bean> <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>helloSpeaker</value> </list> </property> <property name="interceptorNames" value="logInterceptor"></property> </bean> </beans>

 在上面的配置中,指定了org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator类的两个属性,beanNames和interceptorNames,beanNames指定了要进行自动代理的目标对象,interceptorNames与前面的几种模式中的这个属性含义相同,指定了提供服务的对象。测试代码为:

package com.test.spring.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class BeforeAdviceTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "com/test/spring/aop/beforeAfterAdvice.xml"); IHello helloSpeaker = (IHello) context.getBean("helloSpeaker"); helloSpeaker.sayHello("longyg"); } }

 看,这次我们不再是获取的代理对象,而是直接获得helloSpeaker目标对象了。由于我们在XML配置文件中指定了为helloSpeaker目标对象进行自动代理,因此当我们获取目标对象时,Spring会自动为其创建代理对象,并根据配置的服务对象提供相应的服务(logInterceptor)。基于这种方式,我们可以把一个服务对象用于多个目标对象,下面我另外定义了一个helloSpeaker2目标对象,它也实现了IHello接口(当然我们也可以实现任务 其他接口):

package com.test.spring.aop; public class HelloSpeaker2 implements IHello { public void sayHello(String name) { System.out.println("Welcome to world," + name); } }

 下面在XML文件中增加对helloSpeaker2目标对象提供服务:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="logInterceptor" class="com.test.spring.aop.LogInterceptor"></bean> <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean> <bean id="helloSpeaker2" class="com.test.spring.aop.HelloSpeaker2"></bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>helloSpeaker</value> <value>helloSpeaker2</value> </list> </property> <property name="interceptorNames" value="logInterceptor"></property> </bean> </beans>

 在beanNames属性中增加了helloSpeaker2目标对象,因此logInterceptor也对它提供服务。我们还可以增加任意多的目标对象。同样,我们也可以为目标对象提供多个服务对象,下面我又定义了一个agumentInterceptor服务对象:

package com.test.spring.aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class AgumentInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation methodInvocation) throws Throwable { String name = null; Object[] args = methodInvocation.getArguments(); for (int i = 0; i < args.length; i++) { if (args[i] instanceof String) { name = (String) args[i]; } } System.out.println("the argument is : " + name); Object result = methodInvocation.proceed(); System.out.println("the argument is proceed"); return result; } }

 

下面在XML文件中增加agumentInterceptor服务对象:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="logInterceptor" class="com.test.spring.aop.LogInterceptor"></bean> <bean id="agumentInterceptor" class="com.test.spring.aop.AgumentInterceptor"></bean> <bean id="helloSpeaker" class="com.test.spring.aop.HelloSpeaker"></bean> <bean id="helloSpeaker2" class="com.test.spring.aop.HelloSpeaker2"></bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>helloSpeaker</value> <value>helloSpeaker2</value> </list> </property> <property name="interceptorNames"> <list> <value>logInterceptor</value> <value>agumentInterceptor</value> </list> </property> </bean> </beans>

 

 

在interceptorNames属性中增加了agumentInterceptor服务对象,这样,agumentInterceptor服务就可以在目标对象上被应用了。5.结束语以上就是Spring AOP提供的几种比较简单且常用的模式,当然,Spring还提供了很多其他的方式,这里不再一一列出。一般情况下,自动代理的模式是我们经常使用的,它的配置很灵活,我认为初学只要能掌握并使用这种模式就可以了。

转载请注明原文地址: https://www.6miu.com/read-4977781.html

最新回复(0)