Aop前置知识
Aop思想的核心:动态代理思想的应用,涉及到的设计模式是代理设计模式。代理设计模式就是通过代理类为目标类进行额外功能的开发,这样设计的好处:解耦合,利于原始类的维护。
代理设计模式vs装饰器设计模式
- 代理设计模式需要增强的功能是额外功能,和原始类中的业务功能没有太大关系。装饰器设计模式是对本职功能的增强。例如转账方法中进行事务的增强,事务只是保证整个转账方法的完整,转账方法和增强的事务方法没有太大的关系,这个就是代理设计模式的体现。在Mybatis里面的一级缓存就是对于查询进行了增强,这个是装饰器设计模式的体现。
- 代理设计模式基于动态字节码实现的,例如JDK,CGLIB,装饰器设计模式实现没有使用动态字节码技术
Spring中进行AOP开发的实现步骤:
- 定义目标类,或者称为业务类
- 定义额外的功能,或者称为通知
- 明确切入点,或称为对哪个业务方法进行增强
- 定义切面,通过生成代理对象来完成目标方法被通知(或者增强方法)进行增强
实现方式
-
基于XML的实现方式,在Spring配置文件中使用XML定义AOP代理和切入点信息。
-
基于注解的方式,通常配置一个类,使用@Configuration和@EnableAspectJAutoProxy启用注解的Aop支持
下面演示一下基于注解的方式实现Aop,这里是用户新增之前开启一个权限的校验,因此使用前置通知(@Before)
java
package com.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = "com.example")
@EnableAspectJAutoProxy // 开启基于注解的aop模式
public class AppConfig {
}
---------------------------------------------------------------------
package com.example.service;
public interface UserService {
public void add();
}
---------------------------------------------------------------------
package com.example.service.impl;
import com.example.service.UserService;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("add方法实现了");
}
}
---------------------------------------------------------------------
package com.example.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspect {
@Before("execution(* com.example.service.impl.UserServiceImpl.*(..))")
public void check() {
System.out.println("开启权限校验。。。");
}
}
---------------------------------------------------------------------
package com.example.test;
import com.example.config.AppConfig;
import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class testAop {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService =(UserService) context.getBean("userService");
userService.add();
}
}

目前是通过代理对象的产生,对于切点方法的增强,因此UserService是一个代理对象,

如果使用CGLIB动态代理,将@EnableAspectJAutoProxy中的proxyTargetClass改为true,默认为false,使用的是jdk底层的动态代理。

JDK动态代理vsCGLIB字节码代理
- JDK动态代理要求目标类必须实现接口,通过字节码技术对接口进行实现,既保留目标类的原始功能,又对原始功能进行增强
- CGLIB根据目标类通过动态代理技术创建目标类的子类,在子类方法中,既保留目标类的原始功能,又对原始功能进行增强
- 如果没有接口,默认使用CDLIB,不能切换为JDK
- JDK动态代理依靠内置的Proxy类中的newProxyInstance方法,参数有类加载器,原始类的字节码文件的数组,命名对象InvocationHandler,其内部有invoke方法,在invoke方法内部实现了目标方法的增强。invoke方法中有代理对象proxy,增强方法的引用以及执行方法需要传递的参数
- CGLIB需要引入第三方支持,基于Enhancer,其中有create方法,方法中参数有目标类的字节码对象,以及对目标对象增强的方法MethodInterceptor
Spring中对两种代理对象的实现方式进行了整合,通过BeanPostProcessor进行代理对象的产生,对Bean进行增强
java
package com.example.processor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启前置增强的操作");
return method.invoke(bean, args);
}
});
}
}
上述代码中的实现方式是对原始类中的所有方法都进行增强,对目标类某种方法进行增强,这种方式是不合理的,Spring中的AspectJ是可以对某个方法进行增强
源码解析
关掉动态代理之后,进入AbstractApplicationContext类,refresh方法中有个this.registerBeanPostProcessors(beanFactory);表示向beanFactory注册BeanPostProcessors。动态代理开关进行两者的对比

AnnotationAwareAspectJAutoProxyCreator就是bean代理对象产生的BeanPostProcssor,是通过注解@EnableAspectJAutoProxy注入的
那么创建代理对象,是在什么时候进行创建的?
AbstractAutowireCapableBeanFactory.class中有个createBean方法,这个方法调用this.createBean方法,createBean方法中有一个this.doCreateBean方法,doCreateBean方法里有创建对象,属性赋值以及初始化,在初始化这行右键,增加条件断点
此时的beanName就是userService,进入初始化方法, 由于bean代理对象的产生是在后置处理方法上,进入this.applyBeanPostProcessorsAfterInitialization(),

此时用的是jdk动态代理


进入warpIfNecessary方法中的
Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));进行代理对象的产生,进入createProxy方法中,有buildProxy,在进入这个buildProxy,return 的是classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader);进入这个getProxy(),进入后,return this.createAopProxy().getProxy(classLoader),进入这个getProxy,return Proxy.newProxyInstance(this.determineClassLoader(classLoader), this.cache.proxiedInterfaces, this);
使用到了newProxyInstance来创建代理对象,表示是JDK内部动态代理的实现,因为JDK动态代理使用Java.lang.reflect.Proxy类中的newProxyInstance方法进行创建键。而如果是CGLIB动态代理的话,会获取到Enhance进行增强.
无论是哪种动态代理,都是在目标类所属的bean在初始化时使用的beanPostProcessors进行代理对象生成,通过@EnableAspectJAutoProxy的属性值来决定是那种动态代理。
Spring AOP的exposeProxy机制
现在创建一个update方法,使用@Before("execution(* com.example.service.impl.UserServiceImpl.*(..))"),对之前的add和update进行增强,得到

但如果是在add方法中调用update()方法,结果是update()方法并没有进行增强,表示现在的update()方法不是通过代理对象进行调用的,而是当前update()方法所属类的对象。如果要是代理对象去取,可以让UserServiceImpl实现ApplicationContextAware接口获取当前Spring应用的上下文,然后从上下文中手动获取代理对象。
java
package com.example.service.impl;
import com.example.service.UserService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService, ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
@Override
public void add() {
System.out.println("add方法实现了");
UserService proxy = (UserService) context.getBean("userService");
proxy.update();
}
@Override
public void update() {
System.out.println("update方法实现了");
}
}
现在实现了ApplicationContextAware接口后,从哪里获取当前spring的ioc容器?
是在bean的初始化时吗?AbstractAutowireCapableBeanFactory.class类中有个doCreateBean方法,此方法中初始化bean打个断点

进入init方法, 其中 ApplicationContextAware接口中的setApplicationContext在invokeAwareMethods这里执行的吗?

java
private void invokeAwareMethods(String beanName, Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
BeanNameAware beanNameAware = (BeanNameAware)bean;
beanNameAware.setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
BeanClassLoaderAware beanClassLoaderAware = (BeanClassLoaderAware)bean;
ClassLoader bcl = this.getBeanClassLoader();
if (bcl != null) {
beanClassLoaderAware.setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware) {
BeanFactoryAware beanFactoryAware = (BeanFactoryAware)bean;
beanFactoryAware.setBeanFactory(this);
}
}
}
由于 ApplicationContextAware不属于上面的接口,因此不在invokeAwareMethods这里执行。本方法(initializeBean)中applyBeanPostProcessorsBeforeInitialization方法,执行postProcessBeforeInitialization,
java
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Aware) {
this.invokeAwareInterfaces(bean);
}
return bean;
}

总结: Application容器的获取是依赖于ApplicationContextAwareProcessor来实现的,ApplicationContextAwareProcessor中的setApplicationContext是在bean初始化时调用applyBeanPostProcessorsAfterInitialization这个方法执行的。
还有没有其他方法呢?有的。
@EnableAspectJAutoProxy(exposeProxy = true)
exposeProxy是否将代理对象和ThreadLocal进行绑定,true为绑定
java
package com.example.service.impl;
import com.example.service.UserService;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("add方法实现了");
// 通过AopContext这个工具类获取ThreadLocal中的代理对象
UserService proxy = (UserService) AopContext.currentProxy();
proxy.update();
}
@Override
public void update() {
System.out.println("update方法实现了");
}
}
AopContext这个工具类获取ThreadLocal中的代理对象是真的吗?是的

那么是什么时候将代理对象和ThreadLocal进行绑定?以jdk动态代理为例子。在JdkDynamicAopProxy.class中的invoke方法中,有,AopContext.setCurrentProxy(proxy)
实际上是在背后使用了一个ThreadLocal
变量来存储当前代理对象。这样,在同一个线程中的任何地方,都可以通过AopContext.currentProxy()
方法获取到当前的代理对象
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
机制主要用于解决内部方法调用无法应用切面逻辑的问题。通过暴露代理对象并手动使用它,可以在必要时绕过这个限制