Spring使用AOP技术封装了动态代理功能,使用起来非常简单且强大。

  1. <dependency>
  2. <groupId>org.aspectj</groupId>
  3. <artifactId>aspectjrt</artifactId>
  4. <version>1.9.6</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.aspectj</groupId>
  8. <artifactId>aspectjweaver</artifactId>
  9. <version>1.9.6</version>
  10. </dependency>

体验一下

我们的目标是使用AOP技术,动态生成代理对象
新建发送短信接口

  1. public interface SmsService {
  2. String send(String message);
  3. }

实现类

  1. public class SmsServiceImpl implements SmsService{
  2. @Override
  3. public String send(String message) {
  4. System.out.println(message);
  5. return null;
  6. }
  7. }

新建类继承MethodInterceptor接口,实现拦截功能

  1. import org.aopalliance.intercept.MethodInterceptor;
  2. import org.aopalliance.intercept.MethodInvocation;
  3. public class LogIntercepter implements MethodInterceptor {
  4. @Override
  5. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  6. System.out.println("Before-------");
  7. //调用目标对象的目标方法
  8. Object object = methodInvocation.proceed();
  9. System.out.println("After-------");
  10. return object;
  11. }
  12. }

XML配置

  1. <bean id="smsService" class="com.lff.send.SmsService" />
  2. <!-- 附加代码-->
  3. <bean id="logIntercepter" class="com.lff.service.LogIntercepter" />
  4. <aop:config>
  5. <!-- 切入点:给哪些类的哪些方法增加附加代码-->
  6. <aop:pointcut id="log" expression="execution(* *(..))"/>
  7. <!-- 附加代码-->
  8. <aop:advisor advice-ref="logIntercepter" pointcut-ref="log" />
  9. </aop:config>

外界使用

  1. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  2. //拿到目标类对象
  3. SmsService target = (SmsService) context.getBean("smsService");
  4. //调用目标方法
  5. target.send("娃哈哈");

打印日志

  1. Before-------
  2. 娃哈哈
  3. After-------

这就实现了动态代理的功能。我们只需关系写好附加代码。在配置文件里面配置哪些类的哪些方法生成需要增加附加代码即可。

切入点表达式

这个哥们总结的挺好

  1. <aop:pointcut id="log" expression="execution(* *(..))"/>

execution( (..))里面星号的含义:
第一个星:权限修饰符、返回值
第二个星:包名、类名、方法名
(..) 表示参数类型,该符号为任意参数
完整的写法

  1. <aop:pointcut id="log" expression="execution(public void ccom.lff.send.SmsService.send(String))"/>

上面代码expression后面的内容就代表要切在什么地方,上面写代表所有类的所有方法都会被切入。
expression后面可以跟什么东西呢?

  1. expression="指示符()"

常见的指示符

execution

  1. expression="execution(* set*(..))"

表示名字以set开头的所有方法都可以

  1. expression="execution(* com.lff.send.SmsService.*(..))"

表示SmsService类下面的所有方法都可以

  1. expression="execution(public * *(..))"

任意公共方法都可以

  1. expression="execution( * com.lff.send.*.*(..))"

表示send包下的任意类的任意方法都可以,不包含子包

  1. expression="execution( * com.lff.send..*.*(..))"

表示send包下的任意类的任意方法都可以,包含子包

  1. expression="execution( * *(String,String))"

包含2个String参数的任意方法

args指示符的用法

上面execution指示符要求写完整,比如包名、类名、方法名啥的都要写清楚。而args可以简写,用于修饰参数。

  1. expression="args(String,String)"

只有两个String参数的任意方法

within指示符,用于修饰execution第二个星的

  1. expression="within(com.lff.send.*)"

表示send包下的所有类的方法都可以,不包含子包

  1. expression="within(com.lff.send..*)"

表示send包下的所有类的方法都可以,包含子包

annotation 注解

  1. expression="@annotation(com.lff.send.LogAnnotation)"

带有LogAnnotation注解的方法都可以

表达式里面也可以使用逻辑运算符&&(and)、|| (or)、!等

  1. <!-- 附加代码-->
  2. <bean id="logIntercepter" class="com.lff.service.LogIntercepter" />
  3. <aop:config>
  4. <!-- 切入点:给哪些类的哪些方法增加附加代码-->
  5. <aop:pointcut id="log1" expression="execution(public * com.lff.send.SmsService.send(..)) ||execution(public * com.lff.send.SmsService.sendEmail(..))"/>
  6. <!-- 附加代码-->
  7. <aop:advisor advice-ref="logIntercepter" pointcut-ref="log1" />
  8. </aop:config>

如果目标方法相互调用,只会被切入一次

  1. public class SmsService {
  2. public String send(String message) {
  3. System.out.println(message);
  4. sendEmail("调用sendEmail方法");
  5. return null;
  6. }
  7. public String sendEmail(String message) {
  8. System.out.println(message);
  9. return null;
  10. }
  11. }

上面send方法里面调用了sendEmail方法。我们看一下调用关系
外界使用

  1. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  2. //拿到目标类对象
  3. SmsService target = (SmsService) context.getBean("smsService");
  4. //调用目标方法
  5. target.send("这是一封短信");

日志信息

  1. Before-------
  2. 这是一封短信
  3. 调用sendEmail方法
  4. After-------

也就是在调用sendEmail方法时,并没有被切入。如果想在调用send方法里面调用sendEmail方法时也被切入怎么办?
解决方法:拿到代理对象去调用

  1. package com.lff.send;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.beans.factory.BeanNameAware;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.context.ApplicationContextAware;
  6. public class SmsService implements ApplicationContextAware , BeanNameAware {
  7. private ApplicationContext context;
  8. private String beanName;
  9. public String send(String message) {
  10. System.out.println(message);
  11. //拿到代理对象后再调用目标方法
  12. context.getBean(beanName,SmsService.class).sendEmail("调用sendEmail方法");
  13. return null;
  14. }
  15. public String sendEmail(String message) {
  16. System.out.println(message);
  17. return null;
  18. }
  19. @Override
  20. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  21. this.context = applicationContext;
  22. }
  23. @Override
  24. public void setBeanName(String s) {
  25. this.beanName = s;
  26. }
  27. }

代理对象肯定要从IOC容器里面拿,我们可以实现ApplicationContextAware拿到容器对象,实现BeanNameAware可以拿到Bean的名称。

外界使用

  1. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  2. //拿到目标类对象
  3. SmsService target = (SmsService) context.getBean("smsService");
  4. //调用目标方法
  5. target.send("这是一封短信");

打印日志

  1. Before-------
  2. 这是一封短信
  3. Before-------
  4. 调用sendEmail方法
  5. After-------
  6. After-------

配置多个pointcut、advice

XML配置

  1. <!-- 附加代码-->
  2. <bean id="logIntercepter" class="com.lff.service.LogIntercepter" />
  3. <aop:config>
  4. <!-- 切入点:给哪些类的哪些方法增加附加代码-->
  5. <aop:pointcut id="log1" expression="execution(public * com.lff.send.SmsService.send(..))"/>
  6. <aop:pointcut id="log2" expression="execution(public * com.lff.send.SmsService.sendEmail(..))"/>
  7. <!-- 附加代码-->
  8. <aop:advisor advice-ref="logIntercepter" pointcut-ref="log1" />
  9. <aop:advisor advice-ref="logIntercepter" pointcut-ref="log2" />
  10. </aop:config>

也可以这样配置

  1. <!-- 附加代码-->
  2. <bean id="logIntercepter" class="com.lff.service.LogIntercepter" />
  3. <aop:config>
  4. <!-- 切入点:给哪些类的哪些方法增加附加代码-->
  5. <aop:pointcut id="log1" expression="execution(public * com.lff.send.SmsService.send(..))"/>
  6. <!-- 附加代码-->
  7. <aop:advisor advice-ref="logIntercepter" pointcut-ref="log1" />
  8. </aop:config>
  9. <aop:config>
  10. <!-- 切入点:给哪些类的哪些方法增加附加代码-->
  11. <aop:pointcut id="log2" expression="execution(public * com.lff.send.SmsService.sendEmail(..))"/>
  12. <!-- 附加代码-->
  13. <aop:advisor advice-ref="logIntercepter" pointcut-ref="log2" />
  14. </aop:config>