【JavaEE进阶】Spring AOP 原理

在之前的博客中 【JavaEE进阶】Spring AOP使用篇_aop多个切点-CSDN博客

我们主要学习了SpringAOP的应用, 接下来我们来学习SpringAOP的原理, 也就是Spring是如何实现AOP的.

SpringAOP 是基于动态代理来实现AOP的,咱们学习内容主要分以下两部分
1.代理模式
2.Spring AOP源码剖析

1.代理模式

定义: 为其他对象提供一种代理以控制对这个对象的访问, 它的作用就是通过提供一个代理类, 让我们在调用目标方法的时候, 不再是直接对目标方法进行调用, 而是通过代理类间接调用.

在某些情况下, 一个对象不适合或者不能直接引用另一个对象, 而代理对象可以在客户端和目标对象之间起到中介的作用.

使用代理前:

使用代码后:

比如房屋租赁:

Subject: 就是提前定义了房东做的事情,交给中介代理,也是中介要做的事情
RealSubiect: 房东
Proxy: 中介

UML 类图如下:

代理模式可以在不修改被代理对象的基础上, 通过扩展代理类, 进行一些功能的附加与增强
根据代理的创建时期, 代理模式分为静态代理动态代理:

静态代理: 由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的
class 文件就已经存在了

动态代理: 在程序运行时,运用反射机制动态创建而成

静态代理:  由程序员创建代理类或特定工具自动生成源代码再对其编译, 在程序运行前代理类的.class 文件就已经存在了
动态代理:  在程序运行时, 运用反射机制动态创建而成

以房东和中介的的关系举例:

1.1 静态代理

以房东和中介的例子模拟静态代理:

1.定义接口(定义房东要做的事情,也是中介需要做的事情)

package com.example.aop.proxy;/*** 业务接口类*/
public interface HouseSubject {void rentHouse();void saleHouse();
}

 2.实现接口(房东出租房子)目标对象:

package com.example.aop.proxy;/*** 业务实现类*/
public class RealHouseSubject implements HouseSubject {@Overridepublic void rentHouse() {System.out.println("我是房东, 我出租房子");}@Overridepublic void saleHouse() {System.out.println("我是房东, 我出售房子");}
}

3.代理(中介,帮房东出租房子)

package com.example.aop.proxy;/*** 静态代理的代理类 (中介)*/public class HouseProxy implements HouseSubject {//房东对象private HouseSubject target;public HouseProxy(HouseSubject target) {this.target = target;}@Overridepublic void rentHouse() {//代理前System.out.println("我是中介, 开始代理");//出租房子target.rentHouse();//出租后System.out.println("我是中介, 结束代理");}@Overridepublic void saleHouse() {//代理前System.out.println("我是中介, 开始代理");//出售房子target.saleHouse();//出租后System.out.println("我是中介, 结束代理");}
}

 测试:

package com.example.aop.proxy;public class Main {public static void main(String[] args) {
//        静态代理HouseProxy proxy = new HouseProxy(new RealHouseSubject());proxy.rentHouse();proxy.saleHouse();System.out.println("========================");HouseSubject houseSubject = new RealHouseSubject();houseSubject.rentHouse();houseSubject.saleHouse();}
}

上面这个代理实现方式就是静态代理(仿佛啥也没干).

从上述程序可以看出, 虽然静态代理也完成了对目标对象的代理, 但是由于代码都写死了, 对目标对象的每个方法的增强都是手动完成的,非常不灵活. 所以日常开发几乎看不到静态代理的场景. 

我们修改接口(Subject)和业务实现类(RealSubject)时, 还需要修改代理类(Proxy).

同样的, 如果有新增接口(Subiect)和业务实现类(RealSubiect), 也需要对每一个业务实现类新增代理类(Proxy). 既然代理的流程是一样的, 有没有一种办法, 让他们通过一个代理类来实现呢?

这就需要用到动态代理技术了 

1.2 动态代理

相比于静态代理来说,动态代理更加灵活.
我们不需要针对每个目标对象都单独创建一个代理对象, 而是把这个创建代理对象的工作推迟到程序运行时由JVM来实现, 也就是说动态代理在程序运行时, 根据需要动态创建生成

比如房屋中介,我不需要提前预测都有哪些业务, 而是业务来了我再根据情况创建

我们还是先看代码再来理解,Java也对动态代理进行了实现, 并给我们提供了一些AP1, 常见的实现方式有两种:


动态代理在我们日常开发中使用的相对较少,但是在框架中几乎是必用的一门技术, 学会了动态代理之后, 对于我们理解和学习各种框架的原理也非常有帮助.

JDK动态代理 

JDK动态代理实现步骤:

定义JDK动态代理类

实现 InvocationHandler 接口

package com.example.aop.proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class JDKInvocationHandler implements InvocationHandler   {private Object target; //目标对象public JDKInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("我是代理, 开始代理");//通过反射, 调用目标对象的方法Object result = method.invoke(target, args);System.out.println("我是代理, 结束代理");return result;}
}

创建一个代理对象并使用:


import java.lang.reflect.Proxy;public class Main {public static void main(String[] args) {//JDK动态代理/***     public static Object newProxyInstance(ClassLoader loader,*                                           Class<?>[] interfaces,*                                           InvocationHandler h) {*     loader: 加载我们代理类的classload*     interfaces: 要实现的接口*     h: 代理要做的事情, 需要实现 InvocationHandler 这个接口*///目标对象RealHouseSubject target = new RealHouseSubject();//动态生成代理对象HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[] {HouseSubject.class},new JDKInvocationHandler(target));proxy.rentHouse();proxy.saleHouse();}
}

JDK动态代理只能代理接口, 不能代理类:

运行成功

 运行失败

CGLIB动态代理

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类
有些场景下,我们的业务代码是直接实现的,并没有接口定义,为了解决这个问题,我们可以用 CGLIB 动态代理机制来解决.

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成.  CGLIB 通过继承方式实现代理,很多知名的开源框架都使用到了CGLIB.

例如 Spring中的 AOP 模块中: 如果目标对象实现了接口,则默认采用 JDK动态代理, 否则采用 CGLIB 动态代理.

CGLIB 动态代理类实现步骤

 添加依赖

和JDK动态代理不同, CGLlB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

自定义 MethodInterceptor(方法拦截器)
实现MethodInterceptor接

package com.example.aop.proxy;import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class CGLibMethodInterceptor implements MethodInterceptor {private Object target;public CGLibMethodInterceptor(Object target) {this.target = target;}/*** 调用代理对象*/@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("我是中介, 开始代理");Object result = method.invoke(target, args);System.out.println("我是中介, 结束代理");return result;}
}

创建代理类,并使用

package com.example.aop.proxy;import org.springframework.cglib.proxy.Enhancer;public class Main {public static void main(String[] args) {//使用CGLib完成代理//代理接口, 运行成功HouseSubject target = new RealHouseSubject();HouseSubject houseSubject = (HouseSubject) Enhancer.create(target.getClass(), new CGLibMethodInterceptor(target));houseSubject.saleHouse();houseSubject.rentHouse();System.out.println("====================");//代理类, 运行成功RealHouseSubject realHouseSubject =  (RealHouseSubject) Enhancer.create(target.getClass(), new CGLibMethodInterceptor(target));realHouseSubject.saleHouse();realHouseSubject.rentHouse();}
}

CGLIB既可以代理接口, 又可以代理类: 

代码简单讲解
1. Methodinterceptor

MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似,它只定义了一个方法 intercept(),用于增强目标方法,

public interface MethodInterceptor extends Callback {/*** 参数说明: * o: 被代理的对象 * method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法) * objects: ⽅法⼊参 * methodProxy: ⽤于调⽤原始⽅法 */Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable;
}

2. Enhancer.create()
Enhancer.create()用来生成一个代理对象

public static Object create(Class type, Callback callback) {//...代码省略 
}

参数说明:

type: 被代理类的类型(类或接口)
callback: 自定义方法拦截器 MethodInterceptor

JDK动态代理和CGLIB动态代理的区别

JDK 可以代理接口, 不可以代理类

CGLib 既可以代理接口, 又可以代理类

JDK 动态代理是 Java 标准库的一部分,不需要额外的依赖。只要使用的是 Java 开发环境,就可以直接使用 JDK 动态代理

CGLIB 是一个第三方库,需要在项目中添加相应的依赖才能使用。

2. SpringAOP 源码阅读

SpringAOP 主要基于两种方式实现的: JDK及 CGLIB 的方式

生成代理对象的逻辑在父类 AbstractAutoProxyCreator 中
Spring 对于 AOP 的实现,基本上都是靠  AnnotationAwareAspectJAutoProxyCreator 去完成


/*** {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation* that wraps each eligible bean with an AOP proxy, delegating to specified interceptors* before invoking the bean itself.** <p>This class distinguishes between "common" interceptors: shared for all proxies it* creates, and "specific" interceptors: unique per bean instance. There need not be any* common interceptors. If there are, they are set using the interceptorNames property.* As with {@link org.springframework.aop.framework.ProxyFactoryBean}, interceptors names* in the current factory are used rather than bean references to allow correct handling* of prototype advisors and interceptors: for example, to support stateful mixins.* Any advice type is supported for {@link #setInterceptorNames "interceptorNames"} entries.** <p>Such auto-proxying is particularly useful if there's a large number of beans that* need to be wrapped with similar proxies, i.e. delegating to the same interceptors.* Instead of x repetitive proxy definitions for x target beans, you can register* one single such post processor with the bean factory to achieve the same effect.** <p>Subclasses can apply any strategy to decide if a bean is to be proxied, e.g. by type,* by name, by definition details, etc. They can also return additional interceptors that* should just be applied to the specific bean instance. A simple concrete implementation is* {@link BeanNameAutoProxyCreator}, identifying the beans to be proxied via given names.** <p>Any number of {@link TargetSourceCreator} implementations can be used to create* a custom target source: for example, to pool prototype objects. Auto-proxying will* occur even if there is no advice, as long as a TargetSourceCreator specifies a custom* {@link org.springframework.aop.TargetSource}. If there are no TargetSourceCreators set,* or if none matches, a {@link org.springframework.aop.target.SingletonTargetSource}* will be used by default to wrap the target bean instance.** @author Juergen Hoeller* @author Rod Johnson* @author Rob Harrop* @author Sam Brannen* @since 13.10.2003* @see #setInterceptorNames* @see #getAdvicesAndAdvisorsForBean* @see BeanNameAutoProxyCreator* @see DefaultAdvisorAutoProxyCreator*/
@SuppressWarnings("serial")
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {/*** Convenience constant for subclasses: Return value for "do not proxy".* @see #getAdvicesAndAdvisorsForBean*/@Nullableprotected static final Object[] DO_NOT_PROXY = null;/*** Convenience constant for subclasses: Return value for* "proxy without additional interceptors, just the common ones".* @see #getAdvicesAndAdvisorsForBean*/protected static final Object[] PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS = new Object[0];/** Logger available to subclasses. */protected final Log logger = LogFactory.getLog(getClass());/** Default is global AdvisorAdapterRegistry. */private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();/*** Indicates whether the proxy should be frozen. Overridden from super* to prevent the configuration from becoming frozen too early.*/private boolean freezeProxy = false;/** Default is no common interceptors. */private String[] interceptorNames = new String[0];private boolean applyCommonInterceptorsFirst = true;@Nullableprivate TargetSourceCreator[] customTargetSourceCreators;@Nullableprivate BeanFactory beanFactory;private final Set<String> targetSourcedBeans = Collections.newSetFromMap(new ConcurrentHashMap<>(16));private final Map<Object, Object> earlyBeanReferences = new ConcurrentHashMap<>(16);private final Map<Object, Class<?>> proxyTypes = new ConcurrentHashMap<>(16);private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256);/*** Set whether the proxy should be frozen, preventing advice* from being added to it once it is created.* <p>Overridden from the superclass to prevent the proxy configuration* from being frozen before the proxy is created.*/@Overridepublic void setFrozen(boolean frozen) {this.freezeProxy = frozen;}@Overridepublic boolean isFrozen() {return this.freezeProxy;}/*** Specify the {@link AdvisorAdapterRegistry} to use.* <p>Default is the global {@link AdvisorAdapterRegistry}.* @see org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry*/public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) {this.advisorAdapterRegistry = advisorAdapterRegistry;}/*** Set custom {@code TargetSourceCreators} to be applied in this order.* If the list is empty, or they all return null, a {@link SingletonTargetSource}* will be created for each bean.* <p>Note that TargetSourceCreators will kick in even for target beans* where no advices or advisors have been found. If a {@code TargetSourceCreator}* returns a {@link TargetSource} for a specific bean, that bean will be proxied* in any case.* <p>{@code TargetSourceCreators} can only be invoked if this post processor is used* in a {@link BeanFactory} and its {@link BeanFactoryAware} callback is triggered.* @param targetSourceCreators the list of {@code TargetSourceCreators}.* Ordering is significant: The {@code TargetSource} returned from the first matching* {@code TargetSourceCreator} (that is, the first that returns non-null) will be used.*/public void setCustomTargetSourceCreators(TargetSourceCreator... targetSourceCreators) {this.customTargetSourceCreators = targetSourceCreators;}/*** Set the common interceptors. These must be bean names in the current factory.* They can be of any advice or advisor type Spring supports.* <p>If this property isn't set, there will be zero common interceptors.* This is perfectly valid, if "specific" interceptors such as matching* Advisors are all we want.*/public void setInterceptorNames(String... interceptorNames) {this.interceptorNames = interceptorNames;}/*** Set whether the common interceptors should be applied before bean-specific ones.* Default is "true"; else, bean-specific interceptors will get applied first.*/public void setApplyCommonInterceptorsFirst(boolean applyCommonInterceptorsFirst) {this.applyCommonInterceptorsFirst = applyCommonInterceptorsFirst;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) {this.beanFactory = beanFactory;}/*** Return the owning {@link BeanFactory}.* May be {@code null}, as this post-processor doesn't need to belong to a bean factory.*/@Nullableprotected BeanFactory getBeanFactory() {return this.beanFactory;}@Override@Nullablepublic Class<?> predictBeanType(Class<?> beanClass, String beanName) {if (this.proxyTypes.isEmpty()) {return null;}Object cacheKey = getCacheKey(beanClass, beanName);return this.proxyTypes.get(cacheKey);}@Overridepublic Class<?> determineBeanType(Class<?> beanClass, String beanName) {Object cacheKey = getCacheKey(beanClass, beanName);Class<?> proxyType = this.proxyTypes.get(cacheKey);if (proxyType == null) {TargetSource targetSource = getCustomTargetSource(beanClass, beanName);if (targetSource != null) {if (StringUtils.hasLength(beanName)) {this.targetSourcedBeans.add(beanName);}}else {targetSource = EmptyTargetSource.forClass(beanClass);}Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);proxyType = createProxyClass(beanClass, beanName, specificInterceptors, targetSource);this.proxyTypes.put(cacheKey, proxyType);}}return (proxyType != null ? proxyType : beanClass);}@Override@Nullablepublic Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) {return null;}@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyBeanReferences.put(cacheKey, bean);return wrapIfNecessary(bean, beanName, cacheKey);}@Override@Nullablepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {Object cacheKey = getCacheKey(beanClass, beanName);if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {if (this.advisedBeans.containsKey(cacheKey)) {return null;}if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return null;}}// Create proxy here if we have a custom TargetSource.// Suppresses unnecessary default instantiation of the target bean:// The TargetSource will handle target instances in a custom fashion.TargetSource targetSource = getCustomTargetSource(beanClass, beanName);if (targetSource != null) {if (StringUtils.hasLength(beanName)) {this.targetSourcedBeans.add(beanName);}Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}return null;}@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {return pvs;  // skip postProcessPropertyValues}/*** Create a proxy with the configured interceptors if the bean is* identified as one to proxy by the subclass.* @see #getAdvicesAndAdvisorsForBean*/@Override@Nullablepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyBeanReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}/*** Build a cache key for the given bean class and bean name.* <p>Note: As of 4.2.3, this implementation does not return a concatenated* class/name String anymore but rather the most efficient cache key possible:* a plain bean name, prepended with {@link BeanFactory#FACTORY_BEAN_PREFIX}* in case of a {@code FactoryBean}; or if no bean name specified, then the* given bean {@code Class} as-is.* @param beanClass the bean class* @param beanName the bean name* @return the cache key for the given class and name*/protected Object getCacheKey(Class<?> beanClass, @Nullable String beanName) {if (StringUtils.hasLength(beanName)) {return (FactoryBean.class.isAssignableFrom(beanClass) ?BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);}else {return beanClass;}}/*** Wrap the given bean if necessary, i.e. if it is eligible for being proxied.* @param bean the raw bean instance* @param beanName the name of the bean* @param cacheKey the cache key for metadata access* @return a proxy wrapping the bean, or the raw bean instance as-is*/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.Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);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;}/*** Return whether the given bean class represents an infrastructure class* that should never be proxied.* <p>The default implementation considers Advices, Advisors and* AopInfrastructureBeans as infrastructure classes.* @param beanClass the class of the bean* @return whether the bean represents an infrastructure class* @see org.aopalliance.aop.Advice* @see org.springframework.aop.Advisor* @see org.springframework.aop.framework.AopInfrastructureBean* @see #shouldSkip*/protected boolean isInfrastructureClass(Class<?> beanClass) {boolean retVal = Advice.class.isAssignableFrom(beanClass) ||Pointcut.class.isAssignableFrom(beanClass) ||Advisor.class.isAssignableFrom(beanClass) ||AopInfrastructureBean.class.isAssignableFrom(beanClass);if (retVal && logger.isTraceEnabled()) {logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]");}return retVal;}/*** Subclasses should override this method to return {@code true} if the* given bean should not be considered for auto-proxying by this post-processor.* <p>Sometimes we need to be able to avoid this happening, e.g. if it will lead to* a circular reference or if the existing target instance needs to be preserved.* This implementation returns {@code false} unless the bean name indicates an* "original instance" according to {@code AutowireCapableBeanFactory} conventions.* @param beanClass the class of the bean* @param beanName the name of the bean* @return whether to skip the given bean* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#ORIGINAL_INSTANCE_SUFFIX*/protected boolean shouldSkip(Class<?> beanClass, String beanName) {return AutoProxyUtils.isOriginalInstance(beanName, beanClass);}/*** Create a target source for bean instances. Uses any TargetSourceCreators if set.* Returns {@code null} if no custom TargetSource should be used.* <p>This implementation uses the "customTargetSourceCreators" property.* Subclasses can override this method to use a different mechanism.* @param beanClass the class of the bean to create a TargetSource for* @param beanName the name of the bean* @return a TargetSource for this bean* @see #setCustomTargetSourceCreators*/@Nullableprotected TargetSource getCustomTargetSource(Class<?> beanClass, String beanName) {// We can't create fancy target sources for directly registered singletons.if (this.customTargetSourceCreators != null &&this.beanFactory != null && this.beanFactory.containsBean(beanName)) {for (TargetSourceCreator tsc : this.customTargetSourceCreators) {TargetSource ts = tsc.getTargetSource(beanClass, beanName);if (ts != null) {// Found a matching TargetSource.if (logger.isTraceEnabled()) {logger.trace("TargetSourceCreator [" + tsc +"] found custom TargetSource for bean with name '" + beanName + "'");}return ts;}}}// No custom TargetSource found.return null;}/*** Create an AOP proxy for the given bean.* @param beanClass the class of the bean* @param beanName the name of the bean* @param specificInterceptors the set of interceptors that is* specific to this bean (may be empty, but not null)* @param targetSource the TargetSource for the proxy,* already pre-configured to access the bean* @return the AOP proxy for the bean* @see #buildAdvisors*/protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false);}private Class<?> createProxyClass(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {return (Class<?>) buildProxy(beanClass, beanName, specificInterceptors, targetSource, true);}private Object buildProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) {AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);if (proxyFactory.isProxyTargetClass()) {// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.for (Class<?> ifc : beanClass.getInterfaces()) {proxyFactory.addInterface(ifc);}}}else {// No proxyTargetClass flag enforced, let's apply our default checks...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);}// Use original ClassLoader if bean class not locally loaded in overriding class loaderClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = smartClassLoader.getOriginalClassLoader();}return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));}/*** Determine whether the given bean should be proxied with its target class rather than its interfaces.* <p>Checks the {@link AutoProxyUtils#PRESERVE_TARGET_CLASS_ATTRIBUTE "preserveTargetClass" attribute}* of the corresponding bean definition.* @param beanClass the class of the bean* @param beanName the name of the bean* @return whether the given bean should be proxied with its target class* @see AutoProxyUtils#shouldProxyTargetClass*/protected boolean shouldProxyTargetClass(Class<?> beanClass, @Nullable String beanName) {return (this.beanFactory instanceof ConfigurableListableBeanFactory clbf &&AutoProxyUtils.shouldProxyTargetClass(clbf, beanName));}/*** Return whether the Advisors returned by the subclass are pre-filtered* to match the bean's target class already, allowing the ClassFilter check* to be skipped when building advisors chains for AOP invocations.* <p>Default is {@code false}. Subclasses may override this if they* will always return pre-filtered Advisors.* @return whether the Advisors are pre-filtered* @see #getAdvicesAndAdvisorsForBean* @see org.springframework.aop.framework.Advised#setPreFiltered*/protected boolean advisorsPreFiltered() {return false;}/*** Determine the advisors for the given bean, including the specific interceptors* as well as the common interceptor, all adapted to the Advisor interface.* @param beanName the name of the bean* @param specificInterceptors the set of interceptors that is* specific to this bean (may be empty, but not null)* @return the list of Advisors for the given bean*/protected Advisor[] buildAdvisors(@Nullable String beanName, @Nullable Object[] specificInterceptors) {// Handle prototypes correctly...Advisor[] commonInterceptors = resolveInterceptorNames();List<Object> allInterceptors = new ArrayList<>();if (specificInterceptors != null) {if (specificInterceptors.length > 0) {// specificInterceptors may equal PROXY_WITHOUT_ADDITIONAL_INTERCEPTORSallInterceptors.addAll(Arrays.asList(specificInterceptors));}if (commonInterceptors.length > 0) {if (this.applyCommonInterceptorsFirst) {allInterceptors.addAll(0, Arrays.asList(commonInterceptors));}else {allInterceptors.addAll(Arrays.asList(commonInterceptors));}}}if (logger.isTraceEnabled()) {int nrOfCommonInterceptors = commonInterceptors.length;int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0);logger.trace("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors +" common interceptors and " + nrOfSpecificInterceptors + " specific interceptors");}Advisor[] advisors = new Advisor[allInterceptors.size()];for (int i = 0; i < allInterceptors.size(); i++) {advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));}return advisors;}/*** Resolves the specified interceptor names to Advisor objects.* @see #setInterceptorNames*/private Advisor[] resolveInterceptorNames() {BeanFactory bf = this.beanFactory;ConfigurableBeanFactory cbf = (bf instanceof ConfigurableBeanFactory _cbf ? _cbf : null);List<Advisor> advisors = new ArrayList<>();for (String beanName : this.interceptorNames) {if (cbf == null || !cbf.isCurrentlyInCreation(beanName)) {Assert.state(bf != null, "BeanFactory required for resolving interceptor names");Object next = bf.getBean(beanName);advisors.add(this.advisorAdapterRegistry.wrap(next));}}return advisors.toArray(new Advisor[0]);}/*** Subclasses may choose to implement this: for example,* to change the interfaces exposed.* <p>The default implementation is empty.* @param proxyFactory a ProxyFactory that is already configured with* TargetSource and interfaces and will be used to create the proxy* immediately after this method returns*/protected void customizeProxyFactory(ProxyFactory proxyFactory) {}/*** Return whether the given bean is to be proxied, what additional* advices (e.g. AOP Alliance interceptors) and advisors to apply.* @param beanClass the class of the bean to advise* @param beanName the name of the bean* @param customTargetSource the TargetSource returned by the* {@link #getCustomTargetSource} method: may be ignored.* Will be {@code null} if no custom target source is in use.* @return an array of additional interceptors for the particular bean;* or an empty array if no additional interceptors but just the common ones;* or {@code null} if no proxy at all, not even with the common interceptors.* See constants DO_NOT_PROXY and PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS.* @throws BeansException in case of errors* @see #DO_NOT_PROXY* @see #PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS*/@Nullableprotected abstract Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName,@Nullable TargetSource customTargetSource) throws BeansException;}

代理工厂有一个重要的属性: proxyTargetClass,默认值为false.也可以通过程序设置

Spring Boot 2.X开始,默认使用CGLIB代理

可以通过配置项 spring.aop.proxy-target-class=false 来进行修改, 设置默认为jdk代理
SpringBoot设置 @EnableAspectJAutoProxy 无效, 因为Spring Boot 默认使用
AopAutoConfiguration 进行装配

我看点进去看代理⼯⼚的代码


/*** Factory for AOP proxies for programmatic use, rather than via declarative* setup in a bean factory. This class provides a simple way of obtaining* and configuring AOP proxy instances in custom user code.** @author Rod Johnson* @author Juergen Hoeller* @author Rob Harrop* @since 14.03.2003*/
@SuppressWarnings("serial")
public class ProxyFactory extends ProxyCreatorSupport {/*** Create a new ProxyFactory.*/public ProxyFactory() {}/*** Create a new ProxyFactory.* <p>Will proxy all interfaces that the given target implements.* @param target the target object to be proxied*/public ProxyFactory(Object target) {setTarget(target);setInterfaces(ClassUtils.getAllInterfaces(target));}/*** Create a new ProxyFactory.* <p>No target, only interfaces. Must add interceptors.* @param proxyInterfaces the interfaces that the proxy should implement*/public ProxyFactory(Class<?>... proxyInterfaces) {setInterfaces(proxyInterfaces);}/*** Create a new ProxyFactory for the given interface and interceptor.* <p>Convenience method for creating a proxy for a single interceptor,* assuming that the interceptor handles all calls itself rather than* delegating to a target, like in the case of remoting proxies.* @param proxyInterface the interface that the proxy should implement* @param interceptor the interceptor that the proxy should invoke*/public ProxyFactory(Class<?> proxyInterface, Interceptor interceptor) {addInterface(proxyInterface);addAdvice(interceptor);}/*** Create a ProxyFactory for the specified {@code TargetSource},* making the proxy implement the specified interface.* @param proxyInterface the interface that the proxy should implement* @param targetSource the TargetSource that the proxy should invoke*/public ProxyFactory(Class<?> proxyInterface, TargetSource targetSource) {addInterface(proxyInterface);setTargetSource(targetSource);}/*** Create a new proxy according to the settings in this factory.* <p>Can be called repeatedly. Effect will vary if we've added* or removed interfaces. Can add and remove interceptors.* <p>Uses a default class loader: Usually, the thread context class loader* (if necessary for proxy creation).* @return the proxy object*/public Object getProxy() {return createAopProxy().getProxy();}/*** Create a new proxy according to the settings in this factory.* <p>Can be called repeatedly. Effect will vary if we've added* or removed interfaces. Can add and remove interceptors.* <p>Uses the given class loader (if necessary for proxy creation).* @param classLoader the class loader to create the proxy with* (or {@code null} for the low-level proxy facility's default)* @return the proxy object*/public Object getProxy(@Nullable ClassLoader classLoader) {return createAopProxy().getProxy(classLoader);}/*** Determine the proxy class according to the settings in this factory.* @param classLoader the class loader to create the proxy class with* (or {@code null} for the low-level proxy facility's default)* @return the proxy class* @since 6.0*/public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {return createAopProxy().getProxyClass(classLoader);}/*** Create a new proxy for the given interface and interceptor.* <p>Convenience method for creating a proxy for a single interceptor,* assuming that the interceptor handles all calls itself rather than* delegating to a target, like in the case of remoting proxies.* @param proxyInterface the interface that the proxy should implement* @param interceptor the interceptor that the proxy should invoke* @return the proxy object* @see #ProxyFactory(Class, org.aopalliance.intercept.Interceptor)*/@SuppressWarnings("unchecked")public static <T> T getProxy(Class<T> proxyInterface, Interceptor interceptor) {return (T) new ProxyFactory(proxyInterface, interceptor).getProxy();}/*** Create a proxy for the specified {@code TargetSource},* implementing the specified interface.* @param proxyInterface the interface that the proxy should implement* @param targetSource the TargetSource that the proxy should invoke* @return the proxy object* @see #ProxyFactory(Class, org.springframework.aop.TargetSource)*/@SuppressWarnings("unchecked")public static <T> T getProxy(Class<T> proxyInterface, TargetSource targetSource) {return (T) new ProxyFactory(proxyInterface, targetSource).getProxy();}/*** Create a proxy for the specified {@code TargetSource} that extends* the target class of the {@code TargetSource}.* @param targetSource the TargetSource that the proxy should invoke* @return the proxy object*/public static Object getProxy(TargetSource targetSource) {if (targetSource.getTargetClass() == null) {throw new IllegalArgumentException("Cannot create class proxy for TargetSource with null target class");}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setTargetSource(targetSource);proxyFactory.setProxyTargetClass(true);return proxyFactory.getProxy();}}

createAopProxy的实现在 DefaultAopProxyFactory中


/*** Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy* or a JDK dynamic proxy.** <p>Creates a CGLIB proxy if one the following is true for a given* {@link AdvisedSupport} instance:* <ul>* <li>the {@code optimize} flag is set* <li>the {@code proxyTargetClass} flag is set* <li>no proxy interfaces have been specified* </ul>** <p>In general, specify {@code proxyTargetClass} to enforce a CGLIB proxy,* or specify one or more interfaces to use a JDK dynamic proxy.** @author Rod Johnson* @author Juergen Hoeller* @author Sebastien Deleuze* @author Sam Brannen* @since 12.03.2004* @see AdvisedSupport#setOptimize* @see AdvisedSupport#setProxyTargetClass* @see AdvisedSupport#setInterfaces*/
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {/*** Singleton instance of this class.* @since 6.0.10*/public static final DefaultAopProxyFactory INSTANCE = new DefaultAopProxyFactory();private static final long serialVersionUID = 7930414337282325166L;@Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (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.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}}/*** Determine whether the supplied {@link AdvisedSupport} has only the* {@link org.springframework.aop.SpringProxy} interface specified* (or no proxy interfaces specified at all).*/private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {Class<?>[] ifcs = config.getProxiedInterfaces();return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));}}

接下来就是创建代理了
JDK动态代理

CGLIB动态代理

CglibAopProxy类中:

@Overridepublic Object getProxy(@Nullable ClassLoader classLoader) {return buildProxy(classLoader, false);}@Overridepublic Class<?> getProxyClass(@Nullable ClassLoader classLoader) {return (Class<?>) buildProxy(classLoader, true);}private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) {if (logger.isTraceEnabled()) {logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());}try {Class<?> rootClass = this.advised.getTargetClass();Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");Class<?> proxySuperClass = rootClass;if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {proxySuperClass = rootClass.getSuperclass();Class<?>[] additionalInterfaces = rootClass.getInterfaces();for (Class<?> additionalInterface : additionalInterfaces) {this.advised.addInterface(additionalInterface);}}// Validate the class, writing log messages as necessary.validateClassIfNecessary(proxySuperClass, classLoader);// Configure CGLIB Enhancer...Enhancer enhancer = createEnhancer();if (classLoader != null) {enhancer.setClassLoader(classLoader);if (classLoader instanceof SmartClassLoader smartClassLoader &&smartClassLoader.isClassReloadable(proxySuperClass)) {enhancer.setUseCache(false);}}enhancer.setSuperclass(proxySuperClass);enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);enhancer.setAttemptLoad(true);enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));Callback[] callbacks = getCallbacks(rootClass);Class<?>[] types = new Class<?>[callbacks.length];for (int x = 0; x < types.length; x++) {types[x] = callbacks[x].getClass();}// fixedInterceptorMap only populated at this point, after getCallbacks call aboveProxyCallbackFilter filter = new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset);enhancer.setCallbackFilter(filter);enhancer.setCallbackTypes(types);// Generate the proxy class and create a proxy instance.// ProxyCallbackFilter has method introspection capability with Advisor access.try {return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));}finally {// Reduce ProxyCallbackFilter to key-only state for its class cache role// in the CGLIB$CALLBACK_FILTER field, not leaking any Advisor state...filter.advised.reduceToAdvisorKey();}}catch (CodeGenerationException | IllegalArgumentException ex) {throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +": Common causes of this problem include using a final class or a non-visible class",ex);}catch (Throwable ex) {// TargetSource.getTarget() failedthrow new AopConfigException("Unexpected AOP exception", ex);}}

回顾:

1.什么是AOP

2.SpringAOP 的实现方式有哪些?

3.SpringAOP 的实现原理 (基于动态代理 1.JDK 2.CGLib)

4. Spring 使用的是那种代理方式?

Spring 默认 proxyTargetClass 值为false, 如果实现了接口, 使用JDK代理, 如果是普通类则使用CGLib代理

SpringBoot从2.x之后, proxyTargetClass值为true, 默认是使用CGLib代理

5. JDK动态代理和CGLib动态代理的区别?

总结 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/468132.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot+vu的二手车交易系统(全套)

一、系统架构 前端&#xff1a;vue | element-ui | html 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk1.8 | mysql | maven | nodejs 二、代码及数据库 三、功能介绍 01. web端-首页1 02. web端-首页2 03. web端-注册 04. web端-登录 05. w…

macOS开发环境配置与应用开发(详细讲解)

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 macOS作为Apple公司推出的桌面操作系统&#xff0c;以其稳定性、优雅的用户界面和强大的开发工具吸引了大量开发者。对于…

TinyVue v3.19.0 正式发布!Tree 组件终于支持虚拟滚动啦!UI 也升级啦,更更符合现代审美~

你好&#xff0c;我是 Kagol&#xff0c;个人公众号&#xff1a;前端开源星球。 我们非常高兴地宣布&#xff0c;2024年10月28日&#xff0c;TinyVue 发布了 v3.19.0 &#x1f389;。 本次 3.19.0 版本主要有以下重大变更&#xff1a; 所有组件全面升级到 OpenTiny Design 新…

鸿蒙进阶篇-type、typeof、类

“在科技的浪潮中&#xff0c;鸿蒙操作系统宛如一颗璀璨的新星&#xff0c;引领着创新的方向。作为鸿蒙开天组&#xff0c;今天我们将一同踏上鸿蒙基础的探索之旅&#xff0c;为您揭开这一神奇系统的神秘面纱。” 各位小伙伴们我们又见面了,我就是鸿蒙开天组,下面让我们进入今…

JavaWeb合集23-文件上传

二十三 、 文件上传 实现效果&#xff1a;用户点击上传按钮、选择上传的头像&#xff0c;确定自动上传&#xff0c;将上传的文件保存到指定的目录中&#xff0c;并重新命名&#xff0c;生成访问链接&#xff0c;返回给前端进行回显。 1、前端实现 vue3AntDesignVue实现 <tem…

1.62亿元!812个项目立项!上海市2024年度“科技创新行动计划”自然科学基金项目立项

本期精选SCI&EI ●IEEE 1区TOP 计算机类&#xff08;含CCF&#xff09;&#xff1b; ●EI快刊&#xff1a;最快1周录用&#xff01; 知网(CNKI)、谷歌学术期刊 ●7天录用-检索&#xff08;100%录用&#xff09;&#xff0c;1周上线&#xff1b; 免费稿件评估 免费匹配期…

Flink安装和Flink CDC实现数据同步

一&#xff0c;Flink 和Flink CDC 1&#xff0c; Flink Apache Flink是一个框架和分布式处理引擎&#xff0c;用于对无界和有界数据流进行有状态计算。 中文文档 Apache Flink Documentation | Apache Flink 官方文档 &#xff1a;https://flink.apache.org Flink 中文社区…

VBA高级应用30例应用3在Excel中的ListObject对象:插入行和列

《VBA高级应用30例》&#xff08;版权10178985&#xff09;&#xff0c;是我推出的第十套教程&#xff0c;教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开&#xff0c;这套教程案例与理论结合&#xff0c;紧贴“实战”&#xff0c;并做“战术总结”&#xff0c;以…

windows运行ffmpeg的脚本报错:av_ts2str、av_ts2timestr、av_err2str => E0029 C4576

问题描述 我目前的环境是&#xff1a; 编辑器&#xff1a; Microsoft Visual Studio Community 2022 (64 位) 运行的脚本是ffmpeg自带的remux样例&#xff0c;只不过我想用c语言执行这个样例。在执行的过程中报错如下图&#xff1a; C4576 后跟初始值设定项列表的带圆括…

如何利用 Python 的爬虫技术获取淘宝天猫商品的价格信息?

以下是使用 Python 的爬虫技术获取淘宝天猫商品价格信息的两种常见方法&#xff1a; 方法一&#xff1a;使用 Selenium 一、环境准备&#xff1a; 安装 selenium 库&#xff1a;在命令行中运行 pip install selenium。下载浏览器驱动&#xff1a;如 ChromeDriver&#xff08;确…

Linux系统程序设计--2. 文件I/O

文件I/O 标准C的I/O FILE结构体 下面只列出了5个成员 可以观察到&#xff0c;有些函数没有FILE类型的结构体指针例如printf主要是一些标准输出&#xff0c;因为其内部用到了stdin&#xff0c;stdout&#xff0c;stderr查找文件所在的位置:find \ -name stat.h查找头文件所…

Spark 中 RDD 的诞生:原理、操作与分区规则

Spark 的介绍与搭建&#xff1a;从理论到实践-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 Spark on YARN&#xff1a;Spark集群模式之Yarn模式的原…

[2024最新] macOS 发起 Bilibili 直播(不使用 OBS)

文章目录 1、B站账号 主播认证2、开启直播3、直播设置添加素材、隐私设置指定窗口添加/删除 窗口 4、其它说明官方直播帮助中心直播工具教程 目前搜到的 macOS 直播教程都比较古早&#xff0c;大部分都使用 OBS&#xff0c;一番探索下来&#xff0c;发现目前已经不需要 OBS了&a…

内核设备树,你真的了解吗?

在嵌入式系统和内核开发中&#xff0c;设备树&#xff08;Device Tree, 简称 DT&#xff09;扮演着至关重要的角色&#xff0c;帮助系统在启动时准确识别硬件配置并匹配合适的驱动程序。虽然设备树应用广泛&#xff0c;但其结构、工作机制及应用细节却不总是被深入理解。本文将…

yelp数据集上识别潜在的热门商家

yelp数据集是研究B2C业态的一个很好的数据集&#xff0c;要识别潜在的热门商家是一个多维度的分析过程&#xff0c;涉及用户行为、商家特征和社区结构等多个因素。从yelp数据集里我们可以挖掘到下面信息有助于识别热门商家 用户评分和评论分析 评分均值: 商家的平均评分是反映其…

Mac如何将多个pdf文件归并到一个

电脑&#xff1a;MacBook Pro M1 操作方式&#xff1a; very easy 选中想要归并的所有pdf文件&#xff0c;然后 右键 -> quick actions -> Create PDF 然后就可以看到将所选pdf文件归并为一个pdf的文件了

华为eNSP实验:IP Source Guard

一&#xff1a;IP Source Guard: IP Source Guard&#xff08;简称IPSG&#xff09;是一种基于二层接口的源IP地址过滤技术&#xff0c;用于防止恶意主机伪造合法主机的IP地址进行网络攻击。以下是对IP Source Guard的详细解析&#xff1a; 基本概念&#xff1a; IP Source Gu…

API接口精准获取商品详情信息案例

在当今数字化时代&#xff0c;电子商务平台的蓬勃发展&#xff0c;使得商品信息的获取变得尤为重要。API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;作为连接前端用户界面与后端服务的桥梁&#xff0c;扮演着至关重要的角色。本文…

比流计算资源效率最高提升 1000 倍,“增量计算”新模式能否颠覆数据分析?

作者 | 关涛 云器科技CTO 数据平台领域发展 20 年&#xff0c;逐渐成为每个企业的基础设施。作为一个进入“普惠期”的领域&#xff0c;当下的架构已经完美了吗&#xff0c;主要问题和挑战是什么&#xff1f;在 2023 年 AI 跃变式爆发的大背景下&#xff0c;数据平台又该如何演…

牧神记开分9.7,2024新国漫巅峰出现了

现在国漫越来越卷了&#xff0c;卷播放量也卷评分。最近&#xff0c;b站上线不久的国漫《牧神记》开分9.7&#xff0c;口碑还是相当不错的&#xff0c;已经和《凡人修仙传》评分齐平。这部国漫仅仅播出4集&#xff0c;为什么就能获得这么高的评分呢&#xff1f;下面就一起来看看…