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{
@Override
public 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 {
@Override
public 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;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
@Override
public 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>