AOP分析

cglib动态代理

Waiter target = new NaiveWaiter();//一个实现了Waiter接口的类
BeforeAdvice advice = new GreetingBeforeAdvice();//一个实现了MethodBeforeAdvice的类 //spring提供的代理工厂 ProxyFactory pf = new ProxyFactory(); //设置代理目标 pf.setTarget(target); //为代理目标添加增强 pf.addAdvice(advice); //生成代理实例 Waiter proxy = (Waiter) pf.getProxy(); proxy.greetTo("Brian"); 
public interface MethodBeforeAdvice extends BeforeAdvice { /** * Callback before a given method is invoked. * @param method method being invoked * @param args arguments to the method * @param target target of the method invocation. May be <code>null</code>. * @throws Throwable if this object wishes to abort the call. */ void before(Method method, Object[] args, Object target) throws Throwable; }

spring提供的代理工厂new ProxyFactory();

//其父类ProxyCreatorSupport
public ProxyCreatorSupport() { this.aopProxyFactory = new DefaultAopProxyFactory(); } 

设置代理目标 pf.setTarget(target);

//进入ProxyFactory的父类AdvisedSupport
public void setTarget(Object target) { setTargetSource(new SingletonTargetSource(target)); } public void setTargetSource(TargetSource targetSource) { this.targetSource = (targetSource != null ? targetSource : EMPTY_TARGET_SOURCE); } public SingletonTargetSource(Object target) { Assert.notNull(target, "Target object must not be null"); this.target = target; } 

为代理目标添加增强pf.addAdvice(advice);

/**
 * List of Advisors. If an Advice is added, it will be wrapped
 * in an Advisor before being added to this List.
 */
private List advisors = new LinkedList(); //advice就是一个声明,跟serializble一样

//进入AdvisedSupport
public void addAdvice(Advice advice) throws AopConfigException { int pos = this.advisors.size(); addAdvice(pos, advice); } public void addAdvice(int pos, Advice advice) throws AopConfigException { Assert.notNull(advice, "Advice must not be null"); if (advice instanceof IntroductionInfo) { addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice)); } else if (advice instanceof DynamicIntroductionAdvice) { throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor"); } else { addAdvisor(pos, new DefaultPointcutAdvisor(advice)); //普通的advice走这里 } } public DefaultPointcutAdvisor(Advice advice) { this(Pointcut.TRUE, advice);//Pointcut.TRUE表示所有方法,所有类都匹配 } public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) { this.pointcut = pointcut; setAdvice(advice); } //进入AbstractGenericPointcutAdvisor public void setAdvice(Advice advice) { this.advice = advice; } public void addAdvisor(int pos, Advisor advisor) throws AopConfigException { if (advisor instanceof IntroductionAdvisor) { validateIntroductionAdvisor((IntroductionAdvisor) advisor); } addAdvisorInternal(pos, advisor); } private void addAdvisorInternal(int pos, Advisor advisor) throws AopConfigException { ... this.advisors.add(pos, advisor); ... } 

生成代理实例pf.getProxy();

//进入ProxyFactory
public Object getProxy() { return createAopProxy().getProxy(); } //进入父类ProxyCreatorSupport protected final synchronized AopProxy createAopProxy() { ... return getAopProxyFactory().createAopProxy(this); } //进入DefaultAopProxyFactory public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); ... if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); //走cglib } else { return new JdkDynamicAopProxy(config); //走jdk动态代理 } } //进入ObjenesisCglibAopProxy的父类CglibAopProxy public CglibAopProxy(AdvisedSupport config) throws AopConfigException { ... this.advised = config; this.advisedDispatcher = new AdvisedDispatcher(this.advised); } //CglibAopProxy public Object getProxy() { return getProxy(null); } public Object getProxy(ClassLoader classLoader) { ... Class<?> rootClass = this.advised.getTargetClass(); Class<?> proxySuperClass = rootClass; ... // Validate the class, writing log messages as necessary. validateClassIfNecessary(proxySuperClass, classLoader); // Configure CGLIB Enhancer... Enhancer enhancer = createEnhancer(); ... enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader)); .... // Generate the proxy class and create a proxy instance. return createProxyClassAndInstance(enhancer, callbacks); } //子类ObjenesisCglibAopProxy实现,无构造器创建对象 protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { Class<?> proxyClass = enhancer.createClass(); Object proxyInstance = null; if (objenesis.isWorthTrying()) { proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache()); } if (proxyInstance == null) { // Regular instantiation via default constructor... proxyInstance = (this.constructorArgs != null ? proxyClass.getConstructor(this.constructorArgTypes).newInstance(this.constructorArgs) : proxyClass.newInstance()); } ((Factory) proxyInstance).setCallbacks(callbacks); return proxyInstance; } 
进入Cglib2AopProxy的静态内部类DynamicAdvisedInterceptor
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { MethodInvocation invocation = null; Object oldProxy = null; boolean setProxyContext = false; Class targetClass = null; Object target = null; try { Object retVal = null; ... target = getTarget(); if (target != null) { targetClass = target.getClass(); } List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) { retVal = methodProxy.invoke(target, args); } else { invocation = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy); retVal = invocation.proceed(); //执行拦截器链,同下文jdk动态代理的aop一样 } retVal = massageReturnTypeIfNecessary(proxy, target, method, retVal); return retVal; } .... } 

指定接口进行jdk动态代理

Waiter target = new NaiveWaiter();
BeforeAdvice advice = new GreetingBeforeAdvice();
//spring提供的代理工厂
ProxyFactory pf = new ProxyFactory(); //指定接口进行代理 pf.setInterfaces(target.getClass().getInterfaces()); //pf.setOptimize(true);//启用优化后则还将使用Cglib2AopProxy代理 //设置代理目标 pf.setTarget(target); //为代理目标添加增强 pf.addAdvice(advice); //生成代理实例 Waiter proxy = (Waiter) pf.getProxy(); 

指定接口进行代理

//进入AdvisedSupport
public void setInterfaces(Class<?>... interfaces) { this.interfaces.clear(); for (Class<?> ifc : interfaces) { addInterface(ifc); } } public void addInterface(Class<?> intf) { ... if (!this.interfaces.contains(intf)) { this.interfaces.add(intf); ... } } ... //获取代理,进入JdkDynamicAopProxy public Object getProxy() { return getProxy(ClassUtils.getDefaultClassLoader()); //获取应用类加载器 } public Object getProxy(ClassLoader classLoader) { ... Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); //获得需要代理的接口 findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); //对Object的方法特殊处理 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);//经典的jdk动态代理 } public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) { Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces(); ... boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);//有没有接口是SpringProxy的子类,这是个标志接口 boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);//opaque表示是否禁止将代理对象转换为Advised对象 int nonUserIfcCount = 0; if (addSpringProxy) { nonUserIfcCount++; } if (addAdvised) { nonUserIfcCount++; } Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount]; System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); if (addSpringProxy) { proxiedInterfaces[specifiedInterfaces.length] = SpringProxy.class; }//一个标志类,由spring生产的 if (addAdvised) { proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class; } //Advised是一个代理配置类 return proxiedInterfaces;//又添加了俩接口 } private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) { for (Class<?> proxiedInterface : proxiedInterfaces) { Method[] methods = proxiedInterface.getDeclaredMethods(); for (Method method : methods) { if (AopUtils.isEqualsMethod(method)) { this.equalsDefined = true; } if (AopUtils.isHashCodeMethod(method)) { this.hashCodeDefined = true; } if (this.equalsDefined && this.hashCodeDefined) { return; //发现有复写equals和hashcode,接下来要进行代理 } } } } 

当调用具体方法时proxy.greetTo("Brian");

final class JdkDynamicAopProxy implements ..., InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation; Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource;//要被代理的对象 Class<?> targetClass = null; Object target = null; try { if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // The target does not implement the equals(Object) method itself. return equals(args[0]); } if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return hashCode(); } if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { //调用了advised的方法 // Service invocations on ProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; ... target = targetSource.getTarget(); //获取代理对象 if (target != null) { targetClass = target.getClass(); } // Get the interception chain for this method. List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // 没有advice,走简单反射 if (chain.isEmpty()) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);//主要针对可变参数 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { // We need to create a method invocation... invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //执行拦截器链的总入口 // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); } ... return retVal; } ... } } public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) { MethodCacheKey cacheKey = new MethodCacheKey(method); List<Object> cached = this.methodCache.get(cacheKey);//缓存机制,毕竟构造耗时 if (cached == null) { cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( this, method, targetClass);//获得某方法对应的Interceptor this.methodCache.put(cacheKey, cached); } return cached; } //进入DefaultAdvisorChainFactory public List<Object> getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, Class<?> targetClass) { List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length); //里面存着MethodInterceptor Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass()); boolean hasIntroductions = hasMatchingIntroductions(config, actualClass); AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();//返回一个DefaultAdvisorAdapterRegistry for (Advisor advisor : config.getAdvisors()) { if (advisor instanceof PointcutAdvisor) {//我们的DefaultPointcutAdvisor // Add it conditionally. PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { //匹配指定类 MethodInterceptor[] interceptors = registry.getInterceptors(advisor); //获得方法拦截器 MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {//匹配指定方法 ... interceptorList.addAll(Arrays.asList(interceptors));//又转成list了 } } } ... } return interceptorList; } //进入DefaultAdvisorAdapterRegistry public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { List<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(3); Advice advice = advisor.getAdvice(); if (advice instanceof MethodInterceptor) { interceptors.add((MethodInterceptor) advice); //如果实现了MethodInterceptor接口直接加入 } for (AdvisorAdapter adapter : this.adapters) { if (adapter.supportsAdvice(advice)) { //是否实现了相应advice interceptors.add(adapter.getInterceptor(advisor)); } } ... return interceptors.toArray(new MethodInterceptor[interceptors.size()]);//转化为数组返回 } //DefaultAdvisorAdapterRegistry在构造时会创建这三种adapter public DefaultAdvisorAdapterRegistry() { registerAdvisorAdapter(new MethodBeforeAdviceAdapter()); registerAdvisorAdapter(new AfterReturningAdviceAdapter()); registerAdvisorAdapter(new ThrowsAdviceAdapter()); } //以MethodBeforeAdviceAdapter为例 public boolean supportsAdvice(Advice advice) { return (advice instanceof MethodBeforeAdvice); } public MethodInterceptor getInterceptor(Advisor advisor) { MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice(); return new MethodBeforeAdviceInterceptor(advice); } protected ReflectiveMethodInvocation( Object proxy, Object target, Method method, Object[] arguments, Class targetClass, List<Object> interceptorsAndDynamicMethodMatchers) { this.proxy = proxy; this.target = target; this.targetClass = targetClass; this.method = BridgeMethodResolver.findBridgedMethod(method); this.arguments = arguments; this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; } 
执行拦截链invocation.proceed()
//进入ReflectiveMethodInvocation
public Object proceed() throws Throwable { // We start with an index of -1 and increment early. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); //拦截器走到最后一个进行普通方法反射调用 } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);//计数累加 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { ... } else { return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } } //进入MethodBeforeAdviceInterceptor public Object invoke(MethodInvocation mi) throws Throwable { this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() ); return mi.proceed(); } //终于进入自己实现的GreetingBeforeAdvicepublic void before(Method method, Object[] args, Object obj) throws Throwable { String clientName = (String) args[0]; System.out.println("How are you!Mr." + clientName + "."); } protected Object invokeJoinpoint() throws Throwable { return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments); } //进入AopUtils public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args) throws Throwable { ... ReflectionUtils.makeAccessible(method); return method.invoke(target, args); //执行target方法 } 
ProxyFactory和ProxyFactoryBean的区别
  • ProxyFactoryBean实现了Spring的FactoryBean接口,所以它跟Spring中的其它FactoryBean一样,都是基于工厂模式来获取一个bean的。
  • ProxyFactoryBean就是用来获取一个对象的代理对象的FactoryBean。它也是继承自ProxyCreatorSupport类的,所以它的功能基本跟ProxyFactory差不多
  • 只是ProxyFactory是用于编程式的创建代理对象。而ProxyFactoryBean用于在Spring的bean容器中创建基于bean的代理对象 image

网上找的图,虽然丑,但画的非常好,把各个组件的关系描绘的分明

Spring配置
<bean id="forumServiceTarget" class="com.brianway.learning.spring.aop.advice.ForumService"/> <bean id="transactionManager" class="com.brianway.learning.spring.aop.advice.TransactionManager"/> <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="transactionManager" p:target-ref="forumServiceTarget" p:proxyTargetClass="false"/> <!--需要将ForumService抽成接口,不然会转用cglib--> 
//从获取bean开始
ForumServiceInterface forumService = (ForumServiceInterface) context.getBean("forumService"); 

//进入AbstractBeanFactory public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); } protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; Object sharedInstance = getSingleton(beanName); //获得ProxyFactoryBean if (sharedInstance != null && args == null) { ... bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } ... return (T) bean; } protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, RootBeanDefinition mbd) { ... if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { return beanInstance; //不是factoryBean直接返回 } Object object = null; if (mbd == null) { object = getCachedObjectForFactoryBean(beanName);//先看缓存 } if (object == null) { FactoryBean<?> factory = (FactoryBean<?>) beanInstance; // Caches object obtained from FactoryBean if it is a singleton. if (mbd == null && containsBeanDefinition(beanName)) { mbd = getMergedLocalBeanDefinition(beanName); } boolean synthetic = (mbd != null && mbd.isSynthetic()); object = getObjectFromFactoryBean(factory, beanName, !synthetic); //factoryBean获取对象 } return object; } protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) { Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { object = doGetObjectFromFactoryBean(factory, beanName); Object alreadyThere = this.factoryBeanObjectCache.get(beanName); //先从缓存找 if (alreadyThere != null) { object = alreadyThere; } else { if (object != null && shouldPostProcess) { object = postProcessObjectFromFactoryBean(object, beanName); //后置处理器 } this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); //放入缓存 } } return (object != NULL_OBJECT ? object : null); } private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) throws BeanCreationException { Object object; ... object = factory.getObject(); ... return object; } 

ProxyFactoryBean获取对象

public Object getObject() throws BeansException {
		initializeAdvisorChain(); //初始化拦截器链
		if (isSingleton()) {
			return getSingletonInstance(); } else { return newPrototypeInstance(); } } private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException { if (this.advisorChainInitialized) { return;//已经初始化过了直接返回 } if (!ObjectUtils.isEmpty(this.interceptorNames)) { ... for (String name : this.interceptorNames) { if (name.endsWith(GLOBAL_SUFFIX)) { ... } else { Object advice; if (this.singleton || this.beanFactory.isSingleton(name)) { advice = this.beanFactory.getBean(name);//从bean获得advice } else { advice = new PrototypePlaceholderAdvisor(name); } addAdvisorOnChainCreation(advice, name); } } } this.advisorChainInitialized = true; } private void addAdvisorOnChainCreation(Object next, String name) { Advisor advisor = namedBeanToAdvisor(next); //转换成相应的advisor addAdvisor(advisor); } private Advisor namedBeanToAdvisor(Object next) { return this.advisorAdapterRegistry.wrap(next); } //进入DefaultAdvisorAdapterRegistry public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException { if (adviceObject instanceof Advisor) { return (Advisor) adviceObject; } ... Advice advice = (Advice) adviceObject; if (advice instanceof MethodInterceptor) { return new DefaultPointcutAdvisor(advice); } for (AdvisorAdapter adapter : this.adapters) { if (adapter.supportsAdvice(advice)) { return new DefaultPointcutAdvisor(advice); } } throw new UnknownAdviceTypeException(advice); } private synchronized Object getSingletonInstance() { if (this.singletonInstance == null) { this.targetSource = freshTargetSource(); if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { Class<?> targetClass = getTargetClass(); setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader)); //代理目标类的所有接口 } super.setFrozen(this.freezeProxy); this.singletonInstance = getProxy(createAopProxy()); } return this.singletonInstance; } private TargetSource freshTargetSource() { if (this.targetName == null) { return this.targetSource; } else { ... } } //进入父类ProxyCreatorSupport,和之前分析一样,就不赘述了 protected final synchronized AopProxy createAopProxy() { ... return getAopProxyFactory().createAopProxy(this); } protected Object getProxy(AopProxy aopProxy) { return aopProxy.getProxy(this.proxyClassLoader); } //进入JdkDynamicAopProxy,生成代理对象 public Object getProxy(ClassLoader classLoader) { Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } 

aspectj注解的代理

  1. 用户通过配置XML <aop:aspectj-autoproxy>或@EnableAspectJAutoProxy注解来开启AOP自动代理对象,spring IOC容器在启动时会将配置的目标对象bean、切面及相关通知信息,解析成Advisor对象对应的BeanDefintion,并把代理对象的配置信息保存在ProxyConfig对象中。
  2. AbstractAutoProxyCreator继承了ProxyConfig,且实现了SmartInstantiationAwareBeanPostProcessor , BeanFactoryAware接口,这意味着当IOC容器启动时,会创建AbstractAutoProxyCreator对象,实现了BeanFactoryAware意味着,在AbstractAutoProxyCreator实例初始化阶段,BeanFactory自动注入DefaultListableBeanFactory工厂给AbstractAutoProxyCreator实例使用,有BeanFactory对象则可以通过getBean方法来访问容器中所有的通知对象Advisor相关信息.
  3. 每当调用BeanFactory取得bean对象时,利用实现了SmartInstantiationAwareBeanPostProcessor的后置处理器功能,BeanFactory会在多个阶段自动调用AbstractAutoProxyCreator这个后置处理器的实现方法,这些实现方法用来判断该Bean是否需要代理,判断Bean是否需要的代理的依据是根据该bean对象是否在IOC容器中存在匹配的一个或多个通知Advisor对象,存在则需要代理,否则,就放过。

Spring Aop只是借鉴aspectj的定义,提供注解驱动的AOP,本质上还是用的aop的动态代理,这种风格的好处就是不需要使用XML进行配置


参考文档:

ajc环境配置:

编译时织入(Compile Time Weaving, CTW) 载入(Load Time Weaving, LTW)时织入。

只需要配 ajc compiler:aspectjtools.jar,其他不勾选,1.7的aspectjtools,字节码选1.5? 注意先清空target目录,再全项目编译

猜你喜欢

转载自www.cnblogs.com/wangsong/p/10274845.html