Spring使用AOP技术封装了动态代理功能,使用起来非常简单且强大。
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.9.6</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version></dependency>
体验一下
我们的目标是使用AOP技术,动态生成代理对象
新建发送短信接口
public interface SmsService {String send(String message);}
实现类
public class SmsServiceImpl implements SmsService{@Overridepublic String send(String message) {System.out.println(message);return null;}}
新建类继承MethodInterceptor接口,实现拦截功能
import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;public class LogIntercepter implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("Before-------");//调用目标对象的目标方法Object object = methodInvocation.proceed();System.out.println("After-------");return object;}}
XML配置
<bean id="smsService" class="com.lff.send.SmsService" /><!-- 附加代码--><bean id="logIntercepter" class="com.lff.service.LogIntercepter" /><aop:config><!-- 切入点:给哪些类的哪些方法增加附加代码--><aop:pointcut id="log" expression="execution(* *(..))"/><!-- 附加代码--><aop:advisor advice-ref="logIntercepter" pointcut-ref="log" /></aop:config>
外界使用
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//拿到目标类对象SmsService target = (SmsService) context.getBean("smsService");//调用目标方法target.send("娃哈哈");
打印日志
Before-------娃哈哈After-------
这就实现了动态代理的功能。我们只需关系写好附加代码。在配置文件里面配置哪些类的哪些方法生成需要增加附加代码即可。
切入点表达式
<aop:pointcut id="log" expression="execution(* *(..))"/>
execution( (..))里面星号的含义:
第一个星:权限修饰符、返回值
第二个星:包名、类名、方法名
(..) 表示参数类型,该符号为任意参数
完整的写法
<aop:pointcut id="log" expression="execution(public void ccom.lff.send.SmsService.send(String))"/>
上面代码expression后面的内容就代表要切在什么地方,上面写代表所有类的所有方法都会被切入。
expression后面可以跟什么东西呢?
expression="指示符()"
常见的指示符
execution
expression="execution(* set*(..))"
表示名字以set开头的所有方法都可以
expression="execution(* com.lff.send.SmsService.*(..))"
表示SmsService类下面的所有方法都可以
expression="execution(public * *(..))"
任意公共方法都可以
expression="execution( * com.lff.send.*.*(..))"
表示send包下的任意类的任意方法都可以,不包含子包
expression="execution( * com.lff.send..*.*(..))"
表示send包下的任意类的任意方法都可以,包含子包
expression="execution( * *(String,String))"
包含2个String参数的任意方法
args指示符的用法
上面execution指示符要求写完整,比如包名、类名、方法名啥的都要写清楚。而args可以简写,用于修饰参数。
expression="args(String,String)"
只有两个String参数的任意方法
within指示符,用于修饰execution第二个星的
expression="within(com.lff.send.*)"
表示send包下的所有类的方法都可以,不包含子包
expression="within(com.lff.send..*)"
表示send包下的所有类的方法都可以,包含子包
annotation 注解
expression="@annotation(com.lff.send.LogAnnotation)"
带有LogAnnotation注解的方法都可以
表达式里面也可以使用逻辑运算符&&(and)、|| (or)、!等
<!-- 附加代码--><bean id="logIntercepter" class="com.lff.service.LogIntercepter" /><aop:config><!-- 切入点:给哪些类的哪些方法增加附加代码--><aop:pointcut id="log1" expression="execution(public * com.lff.send.SmsService.send(..)) ||execution(public * com.lff.send.SmsService.sendEmail(..))"/><!-- 附加代码--><aop:advisor advice-ref="logIntercepter" pointcut-ref="log1" /></aop:config>
如果目标方法相互调用,只会被切入一次
public class SmsService {public String send(String message) {System.out.println(message);sendEmail("调用sendEmail方法");return null;}public String sendEmail(String message) {System.out.println(message);return null;}}
上面send方法里面调用了sendEmail方法。我们看一下调用关系
外界使用
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//拿到目标类对象SmsService target = (SmsService) context.getBean("smsService");//调用目标方法target.send("这是一封短信");
日志信息
Before-------这是一封短信调用sendEmail方法After-------
也就是在调用sendEmail方法时,并没有被切入。如果想在调用send方法里面调用sendEmail方法时也被切入怎么办?
解决方法:拿到代理对象去调用
package com.lff.send;import org.springframework.beans.BeansException;import org.springframework.beans.factory.BeanNameAware;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;public class SmsService implements ApplicationContextAware , BeanNameAware {private ApplicationContext context;private String beanName;public String send(String message) {System.out.println(message);//拿到代理对象后再调用目标方法context.getBean(beanName,SmsService.class).sendEmail("调用sendEmail方法");return null;}public String sendEmail(String message) {System.out.println(message);return null;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.context = applicationContext;}@Overridepublic void setBeanName(String s) {this.beanName = s;}}
代理对象肯定要从IOC容器里面拿,我们可以实现ApplicationContextAware拿到容器对象,实现BeanNameAware可以拿到Bean的名称。
外界使用
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//拿到目标类对象SmsService target = (SmsService) context.getBean("smsService");//调用目标方法target.send("这是一封短信");
打印日志
Before-------这是一封短信Before-------调用sendEmail方法After-------After-------
配置多个pointcut、advice
XML配置
<!-- 附加代码--><bean id="logIntercepter" class="com.lff.service.LogIntercepter" /><aop:config><!-- 切入点:给哪些类的哪些方法增加附加代码--><aop:pointcut id="log1" expression="execution(public * com.lff.send.SmsService.send(..))"/><aop:pointcut id="log2" expression="execution(public * com.lff.send.SmsService.sendEmail(..))"/><!-- 附加代码--><aop:advisor advice-ref="logIntercepter" pointcut-ref="log1" /><aop:advisor advice-ref="logIntercepter" pointcut-ref="log2" /></aop:config>
也可以这样配置
<!-- 附加代码--><bean id="logIntercepter" class="com.lff.service.LogIntercepter" /><aop:config><!-- 切入点:给哪些类的哪些方法增加附加代码--><aop:pointcut id="log1" expression="execution(public * com.lff.send.SmsService.send(..))"/><!-- 附加代码--><aop:advisor advice-ref="logIntercepter" pointcut-ref="log1" /></aop:config><aop:config><!-- 切入点:给哪些类的哪些方法增加附加代码--><aop:pointcut id="log2" expression="execution(public * com.lff.send.SmsService.sendEmail(..))"/><!-- 附加代码--><aop:advisor advice-ref="logIntercepter" pointcut-ref="log2" /></aop:config>
