Cannot subclass final class class com.sun.proxy.$Proxy


背景

這個錯誤是我在使用AOP動態切換數據庫,實現數據庫的讀寫分離的時候出現的問題,使用到的系統環境是:

<spring.version>3.2.6.RELEASE</spring.version> <mybatis.version>3.2.4</mybatis.version> <mybatis-spring.version>1.1.1</mybatis-spring.version>

使用的代碼

執行切點的代碼是:

package com.xuliugen.choosedb.demo.aspect;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

/**
 * 切換數據源(不同方法調用不同數據源)
 */
@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {

    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    //這個包是存放MyBatis的sql的包
    @Pointcut("execution(* com.xuliugen.choosedb.demo.mybatis.dao.*.*(..))")
    public void aspect() {
    }

    /**
     * 配置前置通知,使用在方法aspect()上注冊的切入點
     */
    @Before("aspect()")
    public void before(JoinPoint point) {
        String className = point.getTarget().getClass().getName();
        String method = point.getSignature().getName();
        logger.info(className + "." + method + "(" + StringUtils.join(point.getArgs(), ",") + ")");
        try {
            for (String key : ChooseDataSource.METHOD_TYPE_MAP.keySet()) {
                for (String type : ChooseDataSource.METHOD_TYPE_MAP.get(key)) {
                    if (method.startsWith(type)) {
                        DataSourceHandler.putDataSource(key);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

代碼的整體意思就說獲取配置文件中的讀、寫數據庫和進行Aop的方法(在下邊的配置文件中可以看到),DataSourceHandler是一個存放數據源的Handler。

配置文件如下:

<!-- 配置動態分配的讀寫 數據源 -->
    <bean id="dataSource" class="com.xuliugen.choosedb.demo.aspect.ChooseDataSource" lazy-init="true">
        <property name="targetDataSources">
            <map key-type="java.lang.String" value-type="javax.sql.DataSource">
                <!-- write -->
                <entry key="write" value-ref="writeDataSource"/>
                <!-- read -->
                <entry key="read" value-ref="readDataSource"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="writeDataSource"/>
        <property name="methodType">
            <map key-type="java.lang.String">
                <!-- read -->
                <entry key="read" value=",get,select,count,list,query"/>
                <!-- write -->
                <entry key="write" value=",add,create,update,delete,remove,"/>
            </map>
        </property>
    </bean>
//這里省去讀寫數據源的配置

  

運行的錯誤信息

這里寫圖片描述

這里寫圖片描述

主要錯誤信息如下:

Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy16]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy16 at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:217) at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:111) at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:477) at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:362) at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:322) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:409) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1655) at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:162) ... 70 more Caused by: java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy16 at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:446) at org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33) at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216) at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:377) at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:285) at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:205) ... 77 more

解決的過程:

Cannot subclass final class class com.sun.proxy.$Proxy16

 

這句錯誤的原因很好理解,就是一個final 不可以被繼承了,即final類不能子類化,因此不能代理proxy。

http://stackoverflow.com/上的一個回答,

You are not injecting an interface so you need to use CGLIB proxies, the spring reference manual states: Spring AOP defaults to using standard J2SE dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied. Spring AOP can also use CGLIB proxies. This is necessary to proxy classes, rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes, business classes normally will implement one or more business interfaces. Spring has decided to use a J2SE proxy (com.sun.proxy.$Proxy57) probably because CrudService implements an interface. To force the use of CGLIB you can tweak your XML: <aop:aspectj-autoproxy proxy-target-class="true"/>

spring AOP can also use CGLIB proxies. This is necessary to proxy classes, rather than interfaces. 可以看出,在默認的情況下如果一個業務類沒有繼承接口的話是會使用CGLIB 的代理方式。CGLib是不能代理final類,或代理被final,private修飾的方法,cglib面對具體類代理,不能是接口。jdk的代理面向的是接口代理。因此如果你的業務類中沒有使用到接口,而是直接使用類的方式,那么在進行 @Autowired或者@Inject的時候會出現錯誤。

兩種的區別:Java動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。而cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。

1、如果目標對象實現了接口,默認情況下會采用JDK的動態代理實現AOP 2、如果目標對象實現了接口,可以強制使用CGLIB實現AOP 3、如果目標對象沒有實現了接口,必須采用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換

如何強制使用CGLIB實現AOP? 
* 在spring配置文件中加入

JDK動態代理和CGLIB字節碼生成的區別? 
* JDK動態代理只能對實現了接口的類生成代理,而不能針對類 
* CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法 
因為是繼承,所以該類或方法最好不要聲明成final


關於Spring的AOP代理方式,詳細可以參考:http://blog.csdn.net/caomiao2006/article/details/51295158

http://blog.csdn.net/caomiao2006/article/details/51297443


他這里提到的解決方式在配置文件中加入:

<aop:aspectj-autoproxy proxy-target-class="true"/>

可以看出我在DataSourceAspect 類上已經加入@EnableAspectJAutoProxy(proxyTargetClass = true) 的注解,效果是一樣的的,因此這種方式不符合我遇到的問題。

因此,如果你在配置文件中進行了配置(<aop:aspectj-autoproxy proxy-target-class="true"/>),並且按照了Spring AOP 提供的代理方式,那么,這種方式是不可以解決的。

切換Spring版本解決問題

可以看出,我使用到的版本是3.2.6.RELEASE,查找相關資料發現,從Spring3.2以后,spring框架本身不在需要cglib這個jar包了,因為cjlib.jar已經被spring項目的jar包集成進去。為了防止項目中其他對cglib版本依賴不一樣的沖突。

根據這個,想到了切換版本,然后將Spring的版本切換到了4.2.5.RELEASE 再次測試,正常運行,錯誤不見了。

可以初步的得到是由於版本的問題造成了這個錯誤的出現,因此對於這個問題可以參考上述的兩種解決方式去解決實際的問題,希望能夠對你的問題有所幫助。

 

來源:http://blog.csdn.net/xlgen157387/article/details/51316814 我也遇到了這個問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM