Spring-AOP的JDK动态代理的局限性

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/dawn_after_dark/article/details/82193512

前言

一个很常规的需求,目标是为服务接口的实现类定义切面来解耦某些与业务无关的操作。期间突然萌生了一个想法:我们知道spring-aop在被通知类拥有的接口的情况下默认采用JDK动态代理模式来织入切面的,而JDK动态代理只会对接口中定义的方法进行拦截,那么被代理中自定义的方法在调用时会发生什么行为呢?

被代理类的接口

package com.special.learningspring.aspect;

public interface Performance {

    String perform();
}

接口实现类

package com.special.learningspring.aspect;

import com.special.learningspring.aspect.Performance;
import org.springframework.stereotype.Component;

/**
 * Created by Special on 2018/6/12 17:51
 */
@Component
public class ImplementsPer implements Performance{

    public String perform() {
        String str = "2323";
        return str + "0000";
    }

    public String myPerform() {
        return "my sucess";
    }
}

切面

package com.special.learningspring.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * Created by Special on 2018/6/12 11:52
 */
@Aspect
@Component
public class Audience {

    //Pointcut定义可重用的切点
    @Pointcut("execution (* com.special.learningspring.aspect.ImplementsPer.perform(..))")
    public void performance() {}

    @Before("performance()")
    public void silenceCellPhones() {
        System.out.println("Silence cell phones");
    }

}

测试类


import com.special.learningspring.aspect.ImplementsPer;
import com.special.learningspring.aspect.Performance;
import com.special.learningspring.config.PerformanceConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * Created by Special on 2018/6/10 15:13
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = PerformanceConfig.class)
public class AspectXMLTest {

    @Autowired
    ImplementsPer implementsPer;

    @Test
    public void test() {
        System.out.println(implementsPer.perform());
    }
}

报错

    D:\program-model-path\jdk1.8\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\software\IDEAProfessional\IntelliJ IDEA 2018.1.6\lib\idea_rt.jar=49825:D:\software\IDEAProfessional\IntelliJ IDEA 2018.1.6\bin" -Dfile.encoding=UTF-8 -classpath "D:\software\IDEAProfessional\IntelliJ IDEA 2018.1.6\lib\idea_rt.jar;D:\software\IDEAProfessional\IntelliJ IDEA 2018.1.6\plugins\junit\lib\junit-rt.jar;D:\software\IDEAProfessional\IntelliJ IDEA 2018.1.6\plugins\junit\lib\junit5-rt.jar;D:\program-model-path\jdk1.8\jre\lib\charsets.jar;D:\program-model-path\jdk1.8\jre\lib\deploy.jar;D:\program-model-path\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\program-model-path\jdk1.8\jre\lib\ext\cldrdata.jar;D:\program-model-path\jdk1.8\jre\lib\ext\dnsns.jar;D:\program-model-path\jdk1.8\jre\lib\ext\jaccess.jar;D:\program-model-path\jdk1.8\jre\lib\ext\jfxrt.jar;D:\program-model-path\jdk1.8\jre\lib\ext\localedata.jar;D:\program-model-path\jdk1.8\jre\lib\ext\nashorn.jar;D:\program-model-path\jdk1.8\jre\lib\ext\sunec.jar;D:\program-model-path\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\program-model-path\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\program-model-path\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\program-model-path\jdk1.8\jre\lib\ext\zipfs.jar;D:\program-model-path\jdk1.8\jre\lib\javaws.jar;D:\program-model-path\jdk1.8\jre\lib\jce.jar;D:\program-model-path\jdk1.8\jre\lib\jfr.jar;D:\program-model-path\jdk1.8\jre\lib\jfxswt.jar;D:\program-model-path\jdk1.8\jre\lib\jsse.jar;D:\program-model-path\jdk1.8\jre\lib\management-agent.jar;D:\program-model-path\jdk1.8\jre\lib\plugin.jar;D:\program-model-path\jdk1.8\jre\lib\resources.jar;D:\program-model-path\jdk1.8\jre\lib\rt.jar;E:\ProgramLearning\learningspring\target\test-classes;E:\ProgramLearning\learningspring\target\classes;C:\Users\94012\.m2\repository\org\springframework\spring-context\4.0.0.RELEASE\spring-context-4.0.0.RELEASE.jar;C:\Users\94012\.m2\repository\org\springframework\spring-beans\4.0.0.RELEASE\spring-beans-4.0.0.RELEASE.jar;C:\Users\94012\.m2\repository\org\springframework\spring-expression\4.0.0.RELEASE\spring-expression-4.0.0.RELEASE.jar;C:\Users\94012\.m2\repository\org\springframework\spring-core\4.0.0.RELEASE\spring-core-4.0.0.RELEASE.jar;C:\Users\94012\.m2\repository\commons-logging\commons-logging\1.1.1\commons-logging-1.1.1.jar;C:\Users\94012\.m2\repository\org\springframework\spring-web\4.0.0.RELEASE\spring-web-4.0.0.RELEASE.jar;C:\Users\94012\.m2\repository\aopalliance\aopalliance\1.0\aopalliance-1.0.jar;C:\Users\94012\.m2\repository\org\springframework\spring-jdbc\4.0.0.RELEASE\spring-jdbc-4.0.0.RELEASE.jar;C:\Users\94012\.m2\repository\org\springframework\spring-tx\4.0.0.RELEASE\spring-tx-4.0.0.RELEASE.jar;C:\Users\94012\.m2\repository\mysql\mysql-connector-java\6.0.6\mysql-connector-java-6.0.6.jar;C:\Users\94012\.m2\repository\junit\junit\4.12\junit-4.12.jar;C:\Users\94012\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;C:\Users\94012\.m2\repository\org\springframework\spring-test\4.0.0.RELEASE\spring-test-4.0.0.RELEASE.jar;C:\Users\94012\.m2\repository\com\alibaba\druid\1.1.10\druid-1.1.10.jar;C:\Users\94012\.m2\repository\org\aspectj\aspectjrt\1.9.1\aspectjrt-1.9.1.jar;C:\Users\94012\.m2\repository\org\springframework\spring-aop\4.0.0.RELEASE\spring-aop-4.0.0.RELEASE.jar;C:\Users\94012\.m2\repository\org\aspectj\aspectjweaver\1.9.1\aspectjweaver-1.9.1.jar;C:\Users\94012\.m2\repository\javax\servlet\javax.servlet-api\4.0.1\javax.servlet-api-4.0.1.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 AspectXMLTest
八月 29, 2018 3:16:27 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext@7f63425a: startup date [Wed Aug 29 15:16:27 CST 2018]; root of context hierarchy
八月 29, 2018 3:16:28 下午 org.springframework.test.context.TestContextManager prepareTestInstance
严重: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@6ab778a] to prepare test instance [AspectXMLTest@4e3958e7]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'AspectXMLTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.special.learningspring.aspect.ImplementsPer AspectXMLTest.implementsPer; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.special.learningspring.aspect.ImplementsPer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:292)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:384)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:326)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:212)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.special.learningspring.aspect.ImplementsPer AspectXMLTest.implementsPer; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.special.learningspring.aspect.ImplementsPer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:508)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
    ... 25 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.special.learningspring.aspect.ImplementsPer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1100)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:960)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:855)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:480)
    ... 27 more


org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'AspectXMLTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.special.learningspring.aspect.ImplementsPer AspectXMLTest.implementsPer; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.special.learningspring.aspect.ImplementsPer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:292)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:384)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:326)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:212)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.special.learningspring.aspect.ImplementsPer AspectXMLTest.implementsPer; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.special.learningspring.aspect.ImplementsPer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:508)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
    ... 25 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.special.learningspring.aspect.ImplementsPer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1100)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:960)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:855)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:480)
    ... 27 more

八月 29, 2018 3:16:28 下午 org.springframework.context.support.AbstractApplicationContext doClose
信息: Closing org.springframework.context.support.GenericApplicationContext@7f63425a: startup date [Wed Aug 29 15:16:27 CST 2018]; root of context hierarchy

Process finished with exit code -1

剖析

之前学习过并理解了动态代理,看来还是不够彻底,如果不深入理解JDK动态代理模式,很容易在写切面的时候误以为可以对该类中所有的方法进行拦截。其实不然,JDK动态代理在运行时产生代理类的时候需要传入被代理的类的加载器,类拥有的接口,方法调用拦截器。

    public static Object newProxyInstance(@Nullable ClassLoader loader,
                                      Class[] interfaces,
                                      org.springframework.cglib.proxy.InvocationHandler h)

它会创建被代理的类接口的实现类,其中对接口中所有定义的方法调用都会被InvocationHandler拦截,所以我们的被代理类中的自定义方法是不会被代理的,当然也不会被切面通知。
那么上面测试的代码为什么会报错的呢?
因为产生代理类的类型是上述接口的新的实现类,类型名称为一般为com.sun.proxy.$Proxy + number, 意味着不能将此类型的对象注入给被代理类的引用。这时spring就会在容器中查找该被代理类的bean实例,但是该bean在postprocess(后置处理)时已经被其代理对象替代,所以会报No qualifying bean of type [com.special.learningspring.aspect.ImplementsPer]

解决方法

注入的引用类型改为接口类型即可,因为代理对象的bean的类型为其实现类,必然可以赋值给该引用。又因为父类的引用无法直接调用子类的自定义方法,所以也在某种程度上保证了出现你认为的bug(你误认为所有的方法都会被增强)。 该代理对象只会对接口中定义的方法进行增强,那么如果我希望要对子类的所有的方法进行增强呢,使用cglib代理模式。
其实JDK动态代理只是代理了接口,只不过在调用接口中方法时会进行额外处理然后转给实际被代理对象处理,cglib则可以真正实现对本类的完全代理,这里就不细说了。

展开阅读全文

没有更多推荐了,返回首页