如下代码在配置完全正常的情况下控制台会有几条输出?
@SpringBootTest
class SpringaopApplicationTests {
@Autowired
private UserService userService;
@Test
void userTest() {
userService.save();
}
}
简单业务代码
public interface UserService {
void save();
void saveContacts();
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("save user logic");
saveContacts();
}
@Override
public void saveContacts() {
System.out.println("save user contacts logic");
}
}
AOP代码
@Aspect
@Component
public class UserAOP {
@Pointcut("execution(public * com.learn.springaop.service..*(..))")
public void servicePointcut() {
}
@After("servicePointcut()")
public void after(JoinPoint joinPoint) throws Throwable {
String name=joinPoint.getSignature().toString();
System.out.println(String.format("日志记录 %s",name));
}
}
对Spring比较了解的同学,答案应该不难得出。答案还是需要卖下关子,等分享完Spring AOP后再揭晓。
我这里不讲高大上的理论,也不讲什么是AOP,AOP与OOP的思想。只讲我们写了一个Spring AOP后为什么能实现这样的结果,spring是如何帮忙我们实现的。本篇是属于AOP系列的第一篇。
这里分享的是基于Spring Boot 2.4.0。
首先看@EnableAspectJAutoProxy 这个注解,注解上的@Import AspectJAutoProxyRegistrar.class
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
}
org.springframework.context.annotation.AspectJAutoProxyRegistrar
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
下面是AnnotationAwareAspectJAutoProxyCreator类的UML图,从UML图我们发现AnnotationAwareAspectJAutoProxyCreator实现了BeanPostProcessor的接口。大家都知道BeanPostProcessor接口在Spring的容器里是Bean实例生命周期里非常重要的一步。BeanPostProcessor的postProcessBeforeInitialization(Object bean, String beanName),postProcessAfterInitialization(Object bean, String beanName)方法可以给开发者对bean实例初始化前和初始化后可以做些自定义的处理。那AnnotationAwareAspectJAutoProxyCreator又对bean做了什么特殊的处理呢?
通过阅读源码我们可以在org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator类实现了BeanPostProcessor的postProcessAfterInitialization方法。从该方法的注释可以看出这个bean的后置处理器是要针对满足条件的bean创建一个代理。其中getCacheKey是获取一个可以缓存的key。主要看下wrapIfNecessary(bean, beanName, cacheKey)这个方法干了啥
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
从wrapIfNecessary里代码可以看到首先从当前bean的class上获取切面和通知,如果获取到则表示当前实例需要执行AOP切面。需要执行AOP后,Spring就针对当前实例生成了一个代理,并且把这个代理实例返回给Spring容器。我们通过getBean操作获取到的就是这代理类的实例。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
//从当前bean实例上获取通知和切面
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
//如果获取的结果不等余null,则为当前bean创建一个代理类
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
//并且返回这个创建的代理后的实例给容器
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean解释了如何获取一个类是否满足AOP的条件。
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
//如果没找到满足的切面则返回null
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
/**
* Find all eligible Advisors for auto-proxying this class.
* @param beanClass the clazz to find advisors for
* @param beanName the name of the currently proxied bean
* @return the empty List, not {@code null},
* if there are no pointcuts or interceptors
* @see #findCandidateAdvisors
* @see #sortAdvisors
* @see #extendAdvisors
*/
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
//找出所有候选的切面,即在BeanFactory里所有类型是Advisor的实例
//具体@Aspectj如何解析成Advisor待分析
List<Advisor> candidateAdvisors = findCandidateAdvisors();
//从所有候选的切面里找满足当前实例的切面
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply
protected List<Advisor> findAdvisorsThatCanApply(
List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
ProxyCreationContext.setCurrentProxiedBeanName(beanName);
try {
return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
}
finally {
ProxyCreationContext.setCurrentProxiedBeanName(null);
}
}
org.springframework.aop.support.AopUtils#findAdvisorsThatCanApply,Pointcut表达式配规则参考org.aspectj.weaver.patterns.SignaturePattern
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new ArrayList<>();
//如果 切面是Introduction类型的切面判断当前实例是否满足条件,Introduction类型一般用得比较少
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
//剩余的其他普通切面是否满足当前实例
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
//返回所有匹配的切面
return eligibleAdvisors;
}
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
//如果是PointcutAdvisor类型的切面,则获取切面的Pointcut配置,判断当前实例是否满足Pointcut配置的条件
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
//这里的pc类型是AspectJExpressionPointcut类型是切点,AspectJExpressionPointcut会根据Pointcut表达式生成匹配器,判断当前实例的class是否满足表达式的类匹配
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
Set<Class<?>> classes = new LinkedHashSet<>();
if (!Proxy.isProxyClass(targetClass)) {
classes.add(ClassUtils.getUserClass(targetClass));
}
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
//判断当前实例的方法是否满足切面的条件,只要有一个方法满足Pointcut表达式则返回true
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if (introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
找到合适的切面后org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy就为当前实例生成一个Proxy
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(getProxyClassLoader());
}
org.springframework.aop.framework.ProxyFactory#getProxy(java.lang.ClassLoader)
public Object getProxy(@Nullable ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!IN_NATIVE_IMAGE &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
//如果当前实例实现了接口则用JdkDynamicAopProxy,否则Cglib的方式
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
这里主要看下JdkDynamicAopProxy方法是如何实现的,重点代码是 Proxy.newProxyInstance,大家都知道Spring的AOP的实现是通过代理实现的,原来是这样代理的。从 Proxy.newProxyInstance传的参数看JdkDynamicAopProxy实现了InvocationHandler接口,代理在执行的时候会调用InvocationHandler的invoke方法,下面看看invoke方法的内容
@Override
public Object getProxy() {
return getProxy(ClassUtils.getDefaultClassLoader());
}
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
//这里不就是JDK的动态代理的实现吗
return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}
可以看到invoke方法里执行的都是切面的通知,具体这些通知如何调用,还是比较复杂的,下一篇分析AOP通知的具体执行过程。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
总结: Spring boot引入spring-boot-starter-aop stater后,开启自动配置会在容器里注册一个实现了BeanPostProcessor的AnnotationAwareAspectJAutoProxyCreator实例。在容器创建每个bean后且实例化后,spring 容器会遍历所有的BeanPostProcessor并执行对应processor的postProcessAfterInitialization方法。AnnotationAwareAspectJAutoProxyCreator的postProcessAfterInitialization会获取所有定义的切面增强(advisors)作为候选,然后循环判断每一个pointcut表达式是否满足当前的实例,如果有满足的advisor,说明该实例需要被AOP增强,则为该实例生成代理实例,并加入Spring容器里,如果没有任何advisor满足则返回原实例。
开篇问题的答案是3条输出:
- save user logic
- save user contacts logic
- 日志记录 void com.learn.springaop.service.impl.UserServiceImpl.save()
结论:既然AOP是通过代理来实现的,且容器里存的实例也是代理后的实例,那么要想增强的部分代码被执行,只要是调用代理的实例肯定都会被执行。那么开篇的问题的结果为什么saveContacts()方法的增强部分没有被执行呢。我们知道调用代理类实例的执行顺序是 Caller->Proxy执行增强的逻辑->真实的实例调用。而在同一个类里面方法间的调用等同与this.saveContacts();而这里的this是指真实的实例而不是被代理的实例。所以他的增强部分代码不会被执行。
破局:如果真实场景遇到这种情况要怎么解决呢。
- 尽量规避这种情况(Spring官方推荐)
- 在对应class里注入实例自己,在方法内部调用时通过注入的实例调用。或者实现ApplicationContextAware通过Object getBean(String name)方式获取实例调用,所有能实现从容器里获取实例的方式都可以。
- 强制转换AopContext.currentProxy()为对应实例的类型后进行调用。Spring官方不推荐使用,因为这样就把代码与AOP耦合了。
延伸:下面是AOP能实现和不能实现的情况
被代理类 | AOP实现方式 | private 方法 | finall方法 | protected方法 | default方法 | 不在接口里的方法 | prototype类型 |
---|---|---|---|---|---|---|---|
实现了接口 | JDK动态代理 | 不存在这种情况 | 不存在这种情况 | Interface必须public | Interface必须public | 否 | 是(每次生成不同实例的代理) |
未实现接口 | Cglib(生成子类) | 否(方法不能被重写) | 否(finall方法不能被重写) | 是(满足java的可见性规则的调用即可,且pointcut表达式允许) | 是(满足java的可见性规则的调用即可,且pointcut表达式允许) | 不存在这种情况 | 是(每次生成不同实例的代理) |