kotlin整合spring cglib问题分析

2020-02-10 10:45:19 浏览数 (1)

近期在整合 kotlin 与 springboot2 shiro vue 的一个脚手架工具,但是在 controller 层出现了一个十分诡异的问题——service 层通过 autowire 注入不成功。

问题代码

pom 中的 plugins 的配置:

代码语言:javascript复制
<plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <executions>
                <execution>
                    <id>compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>test-compile</id>
                    <phase>test-compile</phase>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
          <configuration>
            <compilerPlugins>
              <!-- Or "jpa" for JPA support -->
              <plugin>no-arg</plugin>
            </compilerPlugins>
            <pluginOptions>
              <option>no-arg:annotation=com.my.Annotation</option>
            </pluginOptions>
            <jvmTarget>1.8</jvmTarget>
          </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <executions>
                <execution>
                    <id>compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>testCompile</id>
                    <phase>test-compile</phase>
                    <goals>
                        <goal>testCompile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>

kotlin controller 中的代码 TestController.kt:

代码语言:javascript复制
@RestController
@RequestMapping("/test")
open class TestController {


    @Resource
    @Lazy
    private var sysGroupService : ISysGroupService



    @GetMapping("/list")
    @RequiresPermissions("group:add")
    fun test(): R {
        println("--------------------")
        println(sysGroupService)
        println("--------------------")
        return R.ok()
    }
}

此时在浏览器中访问:http://localhost:8080/api/test/list的方法栈如下:

图中可以看出,spring cglib 未生效,输出的结果也证实了这一点:

代码语言:javascript复制
--------------------
null
--------------------

相同的 java controller 中的代码:

代码语言:javascript复制
@RestController
@RequestMapping("/testtest")
public class TestJavaController {

    @Resource
    private ISysGroupService sysGroupService;

    @GetMapping("/list")
    @RequiresPermissions("group:add")
    public R test(){
        System.out.println("-------------------------");
        System.out.println(sysGroupService);
        System.out.println("-------------------------");
        return R.ok();
    }
}

在浏览器中访问:http://localhost:8080/api/testtest/list 查看方法栈如下:

这里的方法是有做增强的,同时输出的结果也证实了这点:

代码语言:javascript复制
-------------------------
com.ambition.business.user.service.impl.SysGroupServiceImpl@6c46dc02
-------------------------

分析

为什么有增强?

因为方法上加了@RequiresPermissions 注解,在之前的 shiro 源码分析文章中我们知道,这些权限注解是通过 MethodInterceptor 实现的,还记得我们使用注解时必须要配置的几个 bean 么,就是下面这几个:

代码语言:javascript复制
/**
     * Shiro生命周期处理器
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

我们看下 AuthorizationAttributeSourceAdvisor 的构造方法:

代码语言:javascript复制
/**
     * Create a new AuthorizationAttributeSourceAdvisor.
     */
    public AuthorizationAttributeSourceAdvisor() {
        setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
    }

关于 AopAllianceAnnotationsAuthorizingMethodInterceptor 内部的分析,请参考之前的 shiro 源码分析系列文章。这里我们只需要知道,加了这几个 bean 之后,会对加有权限注解的方法使用 spring aop cglib 做一个代理增强。

为什么在 kotlin 中没有这种增强效果呢?

这个问题我也困惑了很久,猜想是语言支持的问题,最后也是通过官方文档(https://spring.io/blog/2016/02/15/developing-spring-boot-applications-with-kotlin)找到一点解释:

代码语言:javascript复制
ou need to use the kotlin-spring plugin to make automatically @Configuration classes and some others @Service or @Repository as open because they cannot be final in Spring due to CGLIB proxy usage (classes and methods in Kotlin are final by default without the open modifier). Beans using JDK dynamic proxies do not require open modifier.

我们接着来看 kotlin-spring 插件的使用:

代码语言:javascript复制
Here's how to use all-open with Maven:

<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>

    <configuration>
        <compilerPlugins>
            <!-- Or "spring" for the Spring support -->
            <plugin>all-open</plugin>
        </compilerPlugins>

        <pluginOptions>
            <!-- Each annotation is placed on its own line -->
            <option>all-open:annotation=com.my.Annotation</option>
            <option>all-open:annotation=com.their.AnotherAnnotation</option>
        </pluginOptions>
    </configuration>

    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-allopen</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
</plugin>

In Maven, enable the spring plugin:

<plugin>spring</plugin>
<compilerPlugins>
    <plugin>spring</plugin>
</compilerPlugins>
The plugin specifies the following annotations: @Component, @Async, @Transactional, @Cacheable and @SpringBootTest. Thanks to meta-annotations support classes annotated with @Configuration, @Controller, @RestController, @Service or @Repository are automatically opened since these annotations are meta-annotated with @Component.

Of course, you can use both kotlin-allopen and kotlin-spring in the same project.

Note that if you use the project template generated by the start.spring.io service, the kotlin-spring plugin will be enabled by default.

详情见:https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin

解决方法

修改 pom 文件如下:

代码语言:javascript复制
<plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <executions>
                <execution>
                    <id>compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>test-compile</id>
                    <phase>test-compile</phase>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <compilerPlugins>
                  <!-- Or "jpa" for JPA support -->
                  <plugin>no-arg</plugin>
                  <!-- Or "spring" for the Spring support -->
                  <plugin>all-open</plugin>
                  <plugin>spring</plugin>
                </compilerPlugins>
                <pluginOptions>
                  <option>no-arg:annotation=com.my.Annotation</option>
                  <!-- Call instance initializers in the synthetic constructor -->
                  <!-- <option>no-arg:invokeInitializers=true</option> -->
                  <!-- Each annotation is placed on its own line -->
                  <option>all-open:annotation=com.my.Annotation</option>
                  <option>all-open:annotation=com.their.AnotherAnnotation</option>
                </pluginOptions>
                <jvmTarget>1.8</jvmTarget>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <executions>
                <execution>
                    <id>compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>testCompile</id>
                    <phase>test-compile</phase>
                    <goals>
                        <goal>testCompile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>

添加依赖:

代码语言:javascript复制
<dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-maven-allopen</artifactId>
        <version>${kotlin.version}</version>
      </dependency>

重新运行,问题解决。

附录

Javassist,ASM 是对class做增强的两个类库。AOP 代理主要分为静态代理和动态代理两大类,静态代理以 AspectJ 为代表;而动态代理则以 spring AOP 为代表。CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,可以不基于接口,但是需要代理的对象不能是final修饰的。Java Proxy是基于接口的动态代理实现。

0 人点赞