Spring AOP开发步骤
- 原始对象(目标对象)
- 额外功能:为原始方法的不同运行时机添加额外功能。
- 切入点:额外功能增加给哪些原始方法,是由切入点决定的。
- 切面:
- 在Spring体系,切面称为Advisor
- 在AspectJ注解形式开发AOP,切面称为Aspect
示例代码
原始对象:
public interface UserService {
void printAge();
void printName();
}
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void printAge() {
System.out.println("UserServiceImpl.printAge");
}
@Override
public void printName() {
System.out.println("UserServiceImpl.printName");
}
}
切面:
切面是由切入点和额外功能组装在一起的。
//@Aspect注解表示这是一个切面
@Aspect
public class MyAspect {
//切入点,表示要给哪些方法加入额外功能
@Pointcut("execution(* edu.hzb.aop.UserService.*(..))")
public void pointCut(){};
/**
下面四个方法都是额外功能
*/
//在原始方法执行之前回调该方法
@Before("pointCut()")
public void myBefore() {
System.out.println("MyAspect.MyBefore");
}
//在原始方法执行之后回调该方法
@After("pointCut()")
public void myAfter() {
System.out.println("MyAspect.MyAfter");
}
//原始方法返回值时回调该方法
// @AfterReturning(value = "pointCut()")
// public void myAfterReturning() {
// System.out.println("MyAspect.myAfterReturning");
// }
//
//原始方法抛出异常时回调该方法
// @AfterThrowing(value = "pointCut()")
// public void myAfterThrowing() {
// System.out.println("MyAspect.myAfterThrowing");
// }
}
配置类:
@Configuration
@ComponentScan("edu.hzb.aop")
//proxyTargetClass=true,表示采用Cglib的方式创建代理
//exposeProxy=true,表示将对象的代理对象存放在ThreadLocal中
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class AppConfig {
}
上述是Spring AOP开发编码的流程,创建代理对象的逻辑就是由@EnableAspectJAutoProxy
注解完成。
分析@EnableAspectJAutoProxy
属性
首先看@EnableAspectJAutoProxy
注解的两个属性
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
proxyTargetClass属性默认为false,表示采用JDK动态代理的方式创建代理对象
proxyTargetClass属性为true,表示采用Cglib动态代理的方式创建代理对象
*/
boolean proxyTargetClass() default false;
/**
exposeProxy直译是暴露代理,exposeProxy=true时会把对象的代理对象存放到ThreadLocal中,
通过AopContext.currentProxy()拿到代理对象
*/
boolean exposeProxy() default false;
}
Spring动态代理创建方式有两种,一种是JDK动态代理;一种是Cglib动态代理。动态代理其实就是通过动态字节码技术,在程序运行的过程中创建代理对象,既保留原始功能,又保留额外功能。
JDK动态代理:原始类通过实现接口的方式。
Cglib动态代理:以继承的方式,创建原始类的子类
以一个问题来举例暴露代理
的用途,还是上面的代码,将原始对象UserServiceImpl
改成如下:
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void printAge() {
System.out.println("UserServiceImpl.printAge");
//在printAge()方法中调用printName()方法
printName();
}
@Override
public void printName() {
System.out.println("UserServiceImpl.printName");
}
}
测试代码:
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.printAge();
}
运行结果:
MyAspect.MyBefore
UserServiceImpl.printAge
UserServiceImpl.printName
MyAspect.MyAfter
程序运行结果显式先执行了额外功能myBefore()
方法,然后执行了printAge()方法,然后内部执行了printName()方法,然后执行了额外功能myAfter()
方法。根据切入点表达式pointCut
可以知道printName()方法执行前后也是要执行额外功能的,但是并没有执行。原因是因为printAge()方法中是由当前对象(原始对象)this来调用printName()方法的,所以在printAge()方法内部调用printName()方法时,也是需要获取UserServiceImpl的代理对象来执行。
解决办法:
1、从工厂中获取代理对象:
通过实现BeanFactoryAware
或ApplicationContextAware
接口,可以将工厂注入到UserServiceImpl
中,通过工厂来获取UserServiceImpl
的代理对象。
@Service("userService")
public class UserServiceImpl implements UserService, ApplicationContextAware {
private BeanFactory beanFactory;
@Override
public void printAge() {
System.out.println("UserServiceImpl.printAge");
//通过获取UserServiceImpl的代理对象来执行printName()方法
UserService userService = (UserService) beanFactory.getBean("userService");
userService.printName();
}
@Override
public void printName() {
System.out.println("UserServiceImpl.printName");
}
//以set注入的方式注入工厂
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.beanFactory = applicationContext;
}
}
运行结果:
在执行printAge()和printName()方法前后都执行了额外功能myBefore()和myAfter()。
MyAspect.MyBefore
UserServiceImpl.printAge
MyAspect.MyBefore
UserServiceImpl.printName
MyAspect.MyAfter
MyAspect.MyAfter
2、从ThreadLocal中获取代理对象
exposeProxy=true
时会把对象的代理对象存放到ThreadLocal中, 通过AopContext.currentProxy()拿到代理对象,在上面的配置类AppConfig.java
中已经将exposeProxy置为true了。
@Service("userService")
public class UserServiceImpl implements UserService{
@Override
public void printAge() {
System.out.println("UserServiceImpl.printAge");
//从ThreadLocal中获取代理对象
UserService userService = (UserService) AopContext.currentProxy();
userService.printName();
}
@Override
public void printName() {
System.out.println("UserServiceImpl.printName");
}
}
通过JDK动态代理或Cglib动态代理创建代理对象时,如果exposeProxy=true
,会把代理对象存放到ThreadLocal中。
/**
AOPContext类的源代码
*/
public final class AopContext {
//存放代理对象
private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");
private AopContext() {
}
public static Object currentProxy() throws IllegalStateException {
Object proxy = currentProxy.get();
if (proxy == null) {
throw new IllegalStateException(
"Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
}
return proxy;
}
@Nullable
static Object setCurrentProxy(@Nullable Object proxy) {
Object old = currentProxy.get();
if (proxy != null) {
currentProxy.set(proxy);
}
else {
currentProxy.remove();
}
return old;
}
}
JdkDynamicAopProxy.java
文件中的invoke
方法:
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
CglibAopProxy.java文件的内部类
DynamicAdvisedInterceptor的
intercept()方法
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}