service層:
@Service
public class MyService {
public String str = new String("hello world");
@Transactional(rollbackFor = Throwable.class)
public void test() {
XXXX
}
}
controller層:
@Controller
public class MyController {
@Autowired
private MyService myService;
@RequestMapping("/test")
public String test() {
log.info("{}", myService.str); // 輸出null
XXXX
}
}
在控制器MyController的test方法中,獲取不到myService.str的值。
由于MyService類使用了@Transactional開啟了事務,所以spring默認會通過cglib創建了一個代理子類(MyService$$EnhancerBySpringCGLIB$$XXXX)代理MyService的行為,在MyController通過@Autowired注入的myService物件實際是cglib動態生成的MyService子類(代理類)物件。因為代理類是MyService的子類,那么被注入的myService物件的父類MyService的str成員屬性也應該被實體化才對,為什么在controller中取到的是null呢?
難道是cglib生成的代理類取不到父類的成員屬性?
于是自己用cglib創建代理類試了一下:
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyProxyFactory implements MethodInterceptor {
//維護目標物件
private Object target;
public MyProxyFactory(Object target) {
this.target = target;
}
//給目標物件創建一個代理物件
public Object getProxyInstance(){
//1.工具類
Enhancer en = new Enhancer();
//2.設定父類
en.setSuperclass(target.getClass());
//3.設定回呼函式
en.setCallback(this);
//4.創建子類(代理物件)
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//執行目標物件的方法
Object returnValue = method.invoke(target, objects);
return returnValue;
}
}
public class SuperA {
public String str = new String("hello world");
public static void main(String[] args) {
SuperA superA = new SuperA();
SuperA proxy = (SuperA) new MyProxyFactory(superA).getProxyInstance();
log.info("proxy: {}", proxy.str); // 輸出"hello world"
}
}
發現自己通過cglib創建的代理物件是能夠取得到父類的成員屬性的。
既然自己通過cglib創建的代理物件是能夠取得到父類的成員屬性,那么為什么spring通過cglib創建的代理卻無法取到被代理類的成員屬性呢?
uj5u.com熱心網友回復:
你的Controller中通過Spring注入的myService物件是cglib生成的代理物件,并且代理物件的父類物件MyService也是Spring生成的,即通過BeanUtils.initiateClass(Constructor)生成的,只執行了MyService類的默認建構式,這兩個物件都沒有通過new指令去獲得;所以也就無法按照JVM正常的物件初始化順序得到物件;
jvm在執行正常的new指令時才會按照類和物件的初始化順序進行物件的初始化,即
1.加載父類(初始化static屬性,賦默認值,執行static塊)
2.加載子類(初始化static屬性,賦默認值,執行static塊)
3.初始化父類物件(初始化非static屬性,賦默認值,執行instance塊,執行建構式)
4.初始化子類物件(初始化非static屬性,賦默認值,執行instance塊,執行建構式);
你的測驗代碼中之所以能取到父類的public成員,是因為你在用cglib的api創建代理物件的時候手動傳入了父類物件,new SuperA();所以jvm執行了正常的物件初始化程序;
-----------------------------分割線
這也就很好的解釋了Spring的設計理念,代碼中除了POJO等資料承載物件,其他的物件都應該從Spring容器中獲取,包括你代碼中的new String("hello world");也是物件,你一旦自己手動new,它就不受Spring管理;
uj5u.com熱心網友回復:
研究了一下原始碼,和最優解答有點出入,貼主就當隨便看看,開拓下思路。
正題:
大家都知道Spring在面沒有介面的類實作代理時,使用了CGLIB,具體可以定位到ObjenesisCglibAopProxy類的createProxyClassAndInstance方法,在這個方法里,通過Enhancer構造了proxy class,然后使用Objenesis 使用proxy class構造一個proxy instance。截圖如下:
Enhancer這個大家都明白,是CGLIB動態生成類的一個入口,網上也有很多相關教程,那么Objenesis是什么?
Objenesis是一個輕量級的類別庫,作用是繞過一個構造器創建一個實體。結合貼主出現的問題,那么就有方法解釋了:Spring使用Objenesis構造這個代理實體時,并沒有通過構造器初始化父類物件以及相關屬性,自然str屬性也為空了。
驗證:
對比驗證以下兩種方式:
使用enhancer構造出來的proxy instance
使用Objenesis+Cglib構造出來的proxy instance
部分核心代碼如下:
//1.工具類
Enhancer en = new Enhancer();
//2.設定父類
en.setSuperclass(MyService.class);
en.setCallbackType(MyMethodInterceptor.class);
Class proxyClass = en.createClass();
//使用cglib構造代理物件并輸出str
System.out.println("cglib:"+((MyService) en.create()).str);
//使用objenesis+cglib構造代理物件
Objenesis objenesis = new ObjenesisStd(true);
Object proxyInstance = objenesis.newInstance(proxyClass);
System.out.println("cglib+objenesis:"+((MyService) proxyInstance).str);
輸出結果:
cglib:hello world
cglib+objenesis:null
結論:
使用cglib構造出來的代理物件依然使用了構造器
objenesis可以繞過構造器創建實體,但父類的相關屬性可能為空
運行環境:
win8+idea2018.3+jdk8+SpringBoot1.4.6.RELEASE
參考地址:
objenesis官方:http://objenesis.org/
objenesis入門教程:https://yq.aliyun.com/ziliao/264583
cglib及其基本使用:https://www.cnblogs.com/xrq730/p/6661692.html
uj5u.com熱心網友回復:
springboot沒有碰到,我記得springboost也是用cglib,spring區分是不是介面選擇,我記得是;
uj5u.com熱心網友回復:
以前對spring熟悉一點,現在幫不了了。~~
uj5u.com熱心網友回復:
說的都很好,但是如果你的service 中增加set get 方法 你會驚奇的發現 點不行 但是get是可以的 你說氣不氣人?各位大佬轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/110682.html
標籤:其他技術討論專區
上一篇:410c如何保持螢屏一直亮著?
