Spring AOP方法嵌套是否能够生效

Spring AOP方法嵌套是否能够生效呢?这是一个面试题!

写例子看效果

为了方便,建立一个SpringBoot项目。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ycxy</groupId>
    <artifactId>springbootbase</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootbase</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

建立一个接口和实现

package com.ycxy.springbootbase.aoptest.proxy;

public interface HelloService {

    String hello();

    String A();

    String B();
}

package com.ycxy.springbootbase.aoptest.proxy;

import org.springframework.stereotype.Service;

@Service
public class HelloServiceImpl implements HelloService{

    @Override
    public String hello() {
        return "Hello";
    }

    @Override
    public String A() {
        System.out.println("A方法里调用B");
        B();
        return null;
    }

    @Override
    public String B() {
        System.out.println("方法B");
        return null;
    }
}

采用编程模式建立一个AOP类。

package com.ycxy.springbootbase.aoptest.proxy;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class HelloAspectProxy {
    @Pointcut(value="execution(* com.ycxy.springbootbase.aoptest.proxy.*.*(..))")
    private void pointCut(){//定义一个切入点 后面的通知直接引入切入点方法pointCut即可            personServerImpl下面的所有方法
    }

    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        System.out.println("before ");
    }

    @After("pointCut()")
    public void after(){
        System.out.println("after ");
    }
}

编写测试案例(test目录下):

package com.ycxy.springbootbase;

import com.ycxy.springbootbase.aoptest.proxy.HelloService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringbootbaseApplicationTests {

    @Autowired
    HelloService  helloService;
    @Test
    void contextLoads() {

        helloService.A();
    }

}

看结果:

before
A方法里调用B
方法B
after

方法B里的切面没有生效!!!!!!!

可是事务嵌套又是怎么回事?

我好像用过呀,嵌套的方法调用,比如在事务注解里有事务嵌套,事务传播,真的用过。那到底是哪个地方出了问题??
再写段代码试试。链接数据库比较麻烦,改改现在的代码。
大致思路是有两个Service,然后嵌套调用。

package com.ycxy.springbootbase.aoptest.proxy1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("helloServiceImpl1")
public class HelloServiceAImpl implements HelloServiceA {

    @Autowired
    HelloServiceB helloServiceB;
    @Override
    public String hello() {
        return "Hello";
    }

    @Override
    public String A() {
        System.out.println("A方法里调用B");

        helloServiceB.B();
        return null;
    }

}

package com.ycxy.springbootbase;

import com.ycxy.springbootbase.aoptest.proxy.HelloService;
import com.ycxy.springbootbase.aoptest.proxy1.HelloServiceA;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringbootbaseApplicationTests {

    @Autowired
    HelloService  helloService;

    @Autowired
    HelloServiceA helloServiceA;

    @Test
    void contextLoads() {
        helloService.A();
    }
    @Test
    void contextLoadsB() {
        System.out.println("不同类下的AOP方法调用");
        helloServiceA.A();
    }
}

运行结果

不同类下的AOP方法调用
before 
A方法里调用B
before 
方法B
after 
after 

这样又可以代理了。

问题是有了找原因吧

到这里,然后,然后,好吧,有必要再认真看看AOP了,先上官网瞧瞧。
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-introduction-defn

找了个别人的图辅助理解:
在这里插入图片描述

结论

Spring AOP在同一个类里自身方法相互调用时是无法拦截的,也就是不生效。
Spring AOP作用下的同一个类里的自身方法调用时会导致被调用方法无法被正确代理。但是将方法写在不同的类中是可以嵌套调用的,比如事务编写。

另外一种表述:
Spring AOP作用下的同一个类里的自身方法不能嵌套调用。

AOP的动态代理原理

https://blog.csdn.net/duoduo18up/article/details/81058477

AOP的动态代理有两种

JDK动态代理

1.能够继承静态代理的全部优点.并且能够实现代码的复用.
2.动态代理可以处理一类业务.只要满足条件 都可以通过代理对象进行处理.
3.动态代理的灵活性不强.
4.JDK 的动态代理要求代理者必须实现接口 , 否则不能生成代理对象. .

Cglib动态代理

1.不管有无接口都可以创建代理对象.
2.cglib创建的代理对象是目标对象的子类.
注意:要在pom中引入cglib依赖

使用spring的AOP代理对象生成策略:
1.在spring中默认条件下如果目标对象有接口,则使用JDK的动态代理.
 如果目标对象没有接口则默认使用cgLib动态代理.
2.当从容器中获取对象时,如果获取的对象满足切入点表达式.那么就会为其创
建代理对象.代理对象指定方法就会执行与切入点绑定的通知方法.

优缺点

jdk静态代理类只能为一个被代理类服务,如果需要代理的类比较多,那么会产生过多的代理类。jdk静态代理在编译时产生class文件,运行时无需产生,可直接使用,效率好。
jdk动态代理必须实现接口,通过反射来动态代理方法,消耗系统性能。但是无需产生过多的代理类,避免了重复代码的产生,系统更加灵活。

cglib动态代理无需实现接口,通过生成子类字节码来实现,比反射快一点,没有性能问题。但是由于cglib会继承被代理类,需要重写被代理方法,所以被代理类不能是final类,被代理方法不能是final。

因此,cglib的应用更加广泛一点。

另外一个版本

差异
AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。
jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。
总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)
还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势

补充
JDK静态代理:jdk静态代理实现比较简单,一般是直接代理对象直接包装了被代理对象

官网解释:

5.8. Proxying Mechanisms 机制
Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. JDK dynamic proxies are built into the JDK, whereas CGLIB is a common open-source class definition library (repackaged into spring-core).

If the target object to be proxied implements at least one interface, a JDK dynamic proxy is used. All of the interfaces implemented by the target type are proxied. If the target object does not implement any interfaces, a CGLIB proxy is created.

If you want to force the use of CGLIB proxying (for example, to proxy every method defined for the target object, not only those implemented by its interfaces), you can do so. However, you should consider the following issues:

With CGLIB, final methods cannot be advised, as they cannot be overridden in runtime-generated subclasses.

As of Spring 4.0, the constructor of your proxied object is NOT called twice anymore, since the CGLIB proxy instance is created through Objenesis. Only if your JVM does not allow for constructor bypassing, you might see double invocations and corresponding debug log entries from Spring’s AOP support.

待办:

  • 后面有必要专门针对动态代理研究一下

参考文章:
Spring AOP 方法内部调用不生效

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值