支撑Java框架的基础技术:泛型,反射,动态代理,cglib
以Spring為例要想看明白他的源碼需要徹底理解Java的一些基礎技術泛型,反射同時對于一些高級技術例如動態代理,cglib和字節碼技術也需要掌握,下面就按章節來一一說清楚這些技術的核心部分,最后手寫一個簡單的Spring框架。
?
一.靜態代碼塊和非靜態代碼塊以及構造函數的調用順序
?
靜態代碼塊:https://stackoverflow.com/questions/2420389/static-initialization-blocks
每次構造類的實例時都會調用它。在靜態塊只被調用一次,當類本身初始化,無論你如何創建該類型的許多對象。
例子二:?
?
下面這個例子用于演示完整的代碼調用順序。
先看父類:?
public class Parent {static String name = "hello";//非靜態代碼塊{System.out.println("1");}//靜態代碼塊static {System.out.println("2");}public Parent() {System.out.println("3");}
}
再看子類:
public class Child extends Parent {static String childName = "hello";{System.out.println("4");}static {System.out.println("5");}public Child() {System.out.println("6");}
}
調用測試的主函數:
public class StaticCodeBlockOrderTest {public static void main(String[] args) {new Child();}
}
輸出:
?
對象的初始化順序:
首先執行父類靜態的內容,父類靜態的內容執行完畢后,接著去執行子類的靜態的內容,當子類的靜態內容執行完畢之后,再去看父類有沒有非靜態代碼塊,
如果有就執行父類的非靜態代碼塊,父類的非靜態代碼塊執行完畢,接著執行父類的構造方法;父類的構造方法執行完畢之后,它接著去看子類有沒有非靜態代碼塊,如果有就執行子類的非靜態代碼塊。
子類的非靜態代碼塊執行完畢再去執行子類的構造方法。
參考:java中靜態代碼塊的用法 static用法詳解
?
靜態代碼塊的應用場景:
需要一個Util類,需要系統初始化的時候就初始化一個hashMap,部分代碼省略以...代替
?
private static Map<String, List<String>> smap = new HashMap<String, List<String>>();static {for (int i = 0; i < k; i++) {List<String> ls = new ArrayList<String>();ls.add(...);ls.add(...);smap.put(..., ls);}}
?
這個一樣的用法:Map的靜態賦值?
?
二.泛型:
?
這個老外的視頻講的很好《Java Generics》
E - Element (在集合中使用,因為集合中存放的是元素)
T - Type(Java 類)
K - Key(鍵)
V - Value(值)
N - Number(數值類型)
? - ?表示不確定的java類型
1.泛型類
https://blog.csdn.net/weixin_43819113/article/details/91042598
https://www.cnblogs.com/coprince/p/8603492.html
泛型類有普通方法返回T,和泛型方法<T> T。
?
2.泛型方法
泛型方法可以不用是泛型類 ,有泛型參數需要才是泛型方法返回值之前需要泛型參數
static 后的<E>是泛型聲明,這樣才可以在這個方法參數和方法體用泛型
static <E>后的Set<E>是返回值
泛型類:?
不用泛型需要強轉:?
輸出:
[123, 456, 789]
[123, 456, 789, 123, 456, 789]
[123, 456, 789]
?
3.泛型 Class<T>和Class<?>的差異
?
public class Box<T> {private T t;public Box(){}public Box(T data){this.t=data;}public T getT() {return t;}public void setT(T t) {this.t = t;}
}
調用
?
?
public static void main(String[] args) {Box<String> s=new Box<String>("abc");Box<Integer> i=new Box<Integer>(123);System.out.println("s class:" + s.getClass());System.out.println("i class:" + i.getClass());System.out.println(s.getClass() == i.getClass());
}
輸出
?
?
為什么有Class<T>還需要Class<?>呢?
其實看這個就明白了
在其他類中例如這個Test里,你可以定義
?
public static void getData(Box<String> data){System.out.println("data :" + data.getT());}
也可以定義
?
public static void getData(Box<Integer> data){System.out.println("data :" + data.getT());}
?
?
但是你要是同時定義這2個就會報錯
名稱沖突: getData(Box<Integer>)和getData(Box<String>)具有相同疑符
?
使用通配符?就可以解決這個問題
?
public class TestMain {public static void main(String[] args) {Box<String> s=new Box<String>("abc");Box<Integer> i=new Box<Integer>(123);System.out.println("s class:" + s.getClass());System.out.println("i class:" + i.getClass());System.out.println(s.getClass() == i.getClass());getData(s);getData(i);}public static void getData(Box<?> data){System.out.println("data :" + data.getT());}
}
?
?
參考:
Java總結篇系列:Java泛型
java中的泛型總結
?
再來看看Class<?>的用處
public class TestMain {public static void main(String[] args) {Box<String> s=new Box<String>("http://blog.csdn.net/unix21");Box<Integer> i=new Box<Integer>(123);System.out.println("s class:" + s.getClass());System.out.println("i class:" + i.getClass());System.out.println(s.getClass() == i.getClass());getData(Box.class);}public static void getData(Class<?> clz){try {System.out.println(clz);System.out.println("clz.hashCode():" + clz.hashCode());Object o=clz.newInstance();o.hashCode();System.out.println("o.hashCode():" + o.hashCode());} catch (Exception e) { System.out.println(e); } }
}
?
((Box)clz).getT();會報錯:
不能將 "class java.lang.Class (no class loader)" 的實例強制轉換為 "class test.Box (loaded by instance of sun.misc.Launcher$AppClassLoader(id=144))" 的實例
說明還沒有class loader
((Box)o).getT();就已經實例化了。
?
4.Object類型
定義的所有類默認都是子類,所有的類都是以標準類Object為基礎,Object類型的變量可以存儲指向任意類類型對象的索引。
當要為一個方法來處理未知類型的對象時,這很有用。
?
//存儲的地方HashMap<String , Object> map = new HashMap<String , Object>();User u1=new User();u1.setId(1);u1.setName("ww1");//Object可以塞任意類型map.put("user",u1);User u=(User)map.get("user");response.getWriter().println("Hello Servlet >>>"+u.getName());String clazz ="com.w1.User"; //bean.getAttributeValue("class");try {//反射Object o = Class.forName(clazz).newInstance();map.put("user2",o);User u2=(User)map.get("user2");u2.setName("ww223");response.getWriter().println("Hello Servlet >>>"+u2.getName());} catch (Exception e) {e.printStackTrace();}
?
三.反射
反射可以參考本人的反射深入專貼:深入淺出Java反射原理和使用場景
這里限于篇幅只羅列一點精華內容。
類名.class, class.forName(), getClass()區別
1:Class cl=A.class; ?
JVM將使用類A的類裝載器, 將類A裝入內存(前提是:類A還沒有裝入內存),不對類A做類的初始化工作.返回類A的Class的對象。
2:Class cl=對象引用o.getClass();
返回引用o運行時真正所指的對象(因為:子對象的引用可能會賦給父對象的引用變量中)所屬的類的Class的對象 。
3:Class.forName("類名");
.裝入類A,并做類的初始化
.getClass()是動態的,其余是靜態的。
.class和class.forName()只能返回類內field的默認值,getClass可以返回當前對象中field的最新值
Class.forName() 返回的是一個類,.newInstance() 后才創建一個對象,Class.forName()的作用是要求JVM查找并加載指定的類,也就是說JVM會執行該類的。
public class Person {private String name = "Alfira";public void getName() {System.out.println(name);}public void setName(String name, int a) {this.name = name + a;}
}
?
private static void show(String name) {try {// JVM將使用類A的類裝載器,將類A裝入內存(前提是:類A還沒有裝入內存),不對類A做類的初始化工作Class classtype3 = Person.class;// 獲得classtype中的方法Method getMethod3 = classtype3.getMethod("getName", new Class[] {});Class[] parameterTypes3 = { String.class, int.class };Method setMethod3 = classtype3.getMethod("setName", parameterTypes3);// 實例化對象,因為這一句才會輸出“靜態初始化”以及“初始化”Object obj3 = classtype3.newInstance();// 通過實例化后的對象調用方法getMethod3.invoke(obj3); // 獲取默認值setMethod3.invoke(obj3, "Setting new ", 3); // 設置getMethod3.invoke(obj3); // 獲取最新System.out.println("----------------");// 返回運行時真正所指的對象Person p = new Person();Class classtype = p.getClass();// Class.forName(name);// 獲得classtype中的方法Method getMethod = classtype.getMethod("getName", new Class[] {});Class[] parameterTypes = { String.class, int.class };Method setMethod = classtype.getMethod("setName", parameterTypes);getMethod.invoke(p);// 獲取默認值setMethod.invoke(p, "Setting new ", 1); // 設置getMethod.invoke(p);// 獲取最新System.out.println("----------------");// 裝入類,并做類的初始化Class classtype2 = Class.forName(name);// 獲得classtype中的方法Method getMethod2 = classtype2.getMethod("getName", new Class[] {});Class[] parameterTypes2 = { String.class, int.class };Method setMethod2 = classtype2.getMethod("setName", parameterTypes2);// 實例化對象Object obj2 = classtype2.newInstance();// 通過實例化后的對象調用方法getMethod2.invoke(obj2); // 獲取默認值setMethod2.invoke(obj2, "Setting new ", 2); // 設置getMethod2.invoke(obj2); // 獲取最新System.out.println("----------------");} catch (Exception e) {System.out.println(e);}}
調用
show("com.Person");
參考此文:http://www.cnblogs.com/feiyun126/archive/2013/08/01/3229492.html
http://blog.163.com/granite8@126/blog/static/853746082008610102657141/
?
四.JDK動態代理和CGLIB動態代理的區別
?
https://stackoverflow.com/questions/10664182/what-is-the-difference-between-jdk-dynamic-proxy-and-cglib
JDK動態代理只能通過接口代理(因此您的目標類需要實現一個接口,然后由代理類實現)。
CGLIB(和javassist)可以通過子類創建代理。在這種情況下,代理成為目標類的子類。不需要接口。
所以Java動態代理可以代理:public class Foo implements iFooCGLIB可以代理的地方:public class Foo
?
我應該提一下,因為javassist和CGLIB通過子類化使用代理,這就是你在使用依賴于它的框架時不能聲明?final方法或使類?final的原因。這將阻止這些庫允許子類化您的類并覆蓋您的方法。
?
還應該注意的是,CGLib子類創建需要足夠了解超類,以便能夠使用正確的args調用正確的構造函數。與基于接口的代理不同,它不關心構造函數。這使得使用CGLib代理比JDK代理更少“自動”。另一個區別在于“堆疊”成本。JDK代理總是在每次調用時產生額外的堆棧幀,而CGLib可能不會花費任何額外的堆棧幀。隨著應用程序越復雜,這變得越來越相關(因為堆棧越大,內存線程消耗越多)。
?
CGLIB不能代理?final方法,但不會拋出異常gist.github.com/mhewedy/7345403cfa52e6f47563f8a204ec0e80?-??Muhammad Hewedy?
?
使用CGLIB庫動態創建代理
CGLIB庫是ASM之上的高級層。它對代理不實現接口的類非常有用。本質上,它動態生成一個子類來覆蓋代理類的非final方法,并連接回調用戶定義的攔截器的鉤子。它比JDK動態代理方法更快。
CGLIB是一個功能強大的高性能代碼生成庫。它是JDK動態代理的補充,因為它提供了不實現接口的代理類。在封面下,它使用ASM字節碼操作框架。實質上,CGLIB動態生成一個子類來覆蓋代理類的非final方法。它比使用Java反射的JDK動態代理方法更快。CGLIB不能使用任何最終方法代理最終類或類。對于一般情況,您使用JDK動態代理方法來創建代理。當接口不可用或性能問題時,CGLIB是一個很好的選擇。
?
https://stackoverflow.com/questions/4411129/why-does-jdk-dynamic-proxy-only-work-with-interfaces
https://www.quora.com/In-Java-why-are-dynamic-proxies-only-allowed-to-proxy-interface-classes
我相信這個決定是在Java標準庫中故意做出的,以支持接口而不是類繼承。使用接口允許以不同方式引用相同的代理對象。
近年來,它也是Java最佳實踐(或“類似Java”),它將對象稱為接口類型而不是類,甚至是匿名類。匿名類通常用作擴展的基礎 - 因此成語類C擴展BaseC實現InterfaceC。
對于更高級的用法,例如增強現有類,使用第三方庫是可以接受的,因為這通常不是由最終用戶開發人員完成的,而是由框架(例如Spring Framework)使用。
http://cliffmeyers.com/blog/2006/12/29/spring-aop-cglib-or-jdk-dynamic-proxies.html
即使您不是面向方面編程的忠實粉絲,如果您使用Spring框架的事務管理,您的應用程序將使用動態AOP代理,盡管是在幕后。Spring可以使用兩種
不同的技術在運行時創建代理:CGLIB或JDK動態代理。
如果目標類實現了一個或多個接口,那么Spring將創建一個實現每個接口的JDK動態代理。如果目標類沒有實現接口,Spring將使用CGLIB動態創建一個新類,它是目標類的子類(“extends”)。這導致了一個重要的區別:JDK動態代理無法轉換為原始目標類,因為它只是一個動態代理,恰好實現了與目標相同的接口。如果在應用程序的模型中使用它們,這會“輕推”您對接口進行編程,因為通常會通過這些接口調用代理。?
另一方面,如果模型中完全沒有接口,Spring將創建CGLIB代理,可以像目標類本身一樣對待或多或少。還有一種方式,迫使其在Spring文檔詳細介紹這兩種情況下CGLIB代理的創作就在這里。
http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html
使用Java Reflection可以在運行時創建接口的動態實現。你這樣做是使用這個類java.lang.reflect.Proxy。這個類的名稱就是我將這些動態接口實現稱為動態代理的原因。動態代理可用于許多不同的目的,例如數據庫連接和事務管理,用于單元測試的動態模擬對象,以及其他類似AOP的方法攔截目的。
已知動態代理至少用于以下目的:
- 數據庫連接和事務管理
- 用于單元測試的動態模擬對象
- DI容器適應自定義工廠接口
- 類似AOP的方法攔截
https://www.logicbig.com/tutorials/core-java-tutorial/java-dynamic-proxies/runtime-interface-implementation.html
https://javax0.wordpress.com/2016/01/20/java-dynamic-proxy/
Java和CGLIB動態代理
?
下面舉例演示使用方法:
public class SayHello {public void say(){System.out.println("hello everyone");}
}
?
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;public class CglibProxy implements MethodInterceptor {private Enhancer enhancer = new Enhancer();public Object getProxy(Class clazz) {//設置需要創建子類的類enhancer.setSuperclass(clazz);enhancer.setCallback(this);//通過字節碼技術動態創建子類實例return enhancer.create();}//實現MethodInterceptor接口方法public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("前置代理");//通過代理類調用父類中的方法Object result = proxy.invokeSuper(obj, args);System.out.println("后置代理");return result;}
}
?
調用
CglibProxy proxy = new CglibProxy();//通過生成子類的方式創建代理類SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);proxyImp.say();
?
CGLib動態代理原理及實現
Java動態代理機制詳解(JDK 和CGLIB,Javassist,ASM)[轉]
cglib動態代理介紹(一)
?
五.自己手寫代碼實現Spring的基本功能
需要說這個作者寫到這一系列文章非常好【SSH進階之路】一步步重構容器實現Spring框架——徹底封裝,實現簡單靈活的Spring框架(十一)
代碼:http://download.csdn.net/detail/jiuqiyuliang/8483981
這篇博文的目標是不僅形似Spring的IoC,而且要神似Spring的IoC,將對象的依賴關系進一步封裝。
完整的項目結構
?
Dao接口和實現
?
public interface Dao {public void daoMethod();
}
public class Dao4MySqlImpl implements Dao {public void daoMethod(){System.out.println("Dao4MySqlImpl.daoMethod()");}
}
public class Dao4OracleImpl implements Dao {public void daoMethod(){System.out.println("Dao4OracleImpl.daoMethod()");}
}
Service接口和實現
?
public interface Service {public void serviceMethod();
}
public class ServiceImpl implements Service {private Dao dao; //依賴注入public void setDao(Dao dao) {this.dao= dao;}@Overridepublic void serviceMethod() {dao.daoMethod();}
}
?
public interface BeanFactory {Object getBean(String beanName);
}
import java.util.ArrayList;
import java.util.List;public class BeanDefinition {private String id;private String className;private List<PropertyDefinition> propertys = new ArrayList<PropertyDefinition>();public BeanDefinition(String id, String className) {this.id = id;this.className = className;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getClassName() {return className;}public void setClassName(String className) {this.className = className;}public List<PropertyDefinition> getPropertys() {return propertys;}public void setPropertys(List<PropertyDefinition> propertys) {this.propertys = propertys;}
}
?
核心容器
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 容器** @author liang**/
public class ClassPathXmlApplicationContext implements BeanFactory {// 用于存放Beanprivate List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();// 用于存放Bean的實例private Map<String, Object> sigletons =new HashMap<String, Object>();public ClassPathXmlApplicationContext(String fileName) {this.readXML(fileName);this.instanceBeans();this.injectObject();}/*** 為bean對象的屬性注入值*/private void injectObject() {for (BeanDefinition beanDefinition :beanDefines) {Object bean = sigletons.get(beanDefinition.getId());if(bean != null){try {// 通過Introspector取得bean的定義信息,之后再取得屬性的描述信息,返回一個數組PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();for(PropertyDefinition propertyDefinition:beanDefinition.getPropertys()){for(PropertyDescriptor properdesc: ps){if(propertyDefinition.getName().equals(properdesc.getName())){// 獲取屬性的setter方法,privateMethod setter = properdesc.getWriteMethod();if(setter != null){Object value = sigletons.get(propertyDefinition.getRef());// 允許訪問私有方法setter.setAccessible(true);// 把引用對象注入到屬性setter.invoke(bean, value);}break;}}}} catch (Exception e) {e.printStackTrace();}}}}/*** 完成bean的實例化*/private void instanceBeans() {for(BeanDefinition beanDefinition : beanDefines){try {if(beanDefinition.getClassName() != null && !"".equals(beanDefinition.getClassName().trim())){sigletons.put(beanDefinition.getId(),Class.forName(beanDefinition.getClassName()).newInstance() );}} catch (Exception e) {e.printStackTrace();}}}/*** 讀取xml配置文件*/private void readXML(String fileName) {// 創建SAXBuilder對象SAXBuilder saxBuilder = new SAXBuilder();try {// 讀取資源,獲得document對象Document doc = saxBuilder.build(this.getClass().getClassLoader().getResourceAsStream(fileName));// 獲取根元素Element rootEle = doc.getRootElement();// 從根元素獲得所有的子元素,建立元素集合List listBean = XPath.selectNodes(rootEle, "/beans/bean");// 遍歷根元素的子元素集合,掃描配置文件中的beanfor (int i = 0; i < listBean.size(); i++) {// 將根元素beans下的bean子元素作為一個新的子根元素Element elementBean = (Element) listBean.get(i);//獲取id屬性值String id = elementBean.getAttributeValue("id");//獲取class屬性值String clazz = elementBean.getAttributeValue("class");BeanDefinition beanDefine = new BeanDefinition(id,clazz);// 獲取子根元素bean下的所有property子元素List listProperty = elementBean.getChildren("property");// 遍歷子根元素的子元素集合(即遍歷property元素)for (int j = 0; j < listProperty.size(); j++) {// 獲取property元素Element elmentProperty = (Element)listProperty.get(j);// 獲取name屬性值String propertyName = elmentProperty.getAttributeValue("name");// 獲取ref屬性值String propertyref = elmentProperty.getAttributeValue("ref");PropertyDefinition propertyDefinition = new PropertyDefinition(propertyName,propertyref);beanDefine.getPropertys().add(propertyDefinition);}// 將javabean添加到集合中beanDefines.add(beanDefine);}} catch (Exception e) {e.printStackTrace();}}/*** 獲取bean實例*/@Overridepublic Object getBean(String beanName) {return this.sigletons.get(beanName);}
}
?
public class PropertyDefinition {private String name;private String ref;public PropertyDefinition(String name, String ref) {this.name = name;this.ref = ref;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getRef() {return ref;}public void setRef(String ref) {this.ref = ref;}}
?
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="dao" class="com.tgb.container.dao.impl.Dao4MySqlImpl" /><bean id="service" class="com.tgb.container.service.impl.ServiceImpl"><property name="dao" ref="dao"></property></bean>
</beans>
?
總結
以上是生活随笔為你收集整理的支撑Java框架的基础技术:泛型,反射,动态代理,cglib的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java多线程的11种创建方式以及纠正网
- 下一篇: 联发科g90t相当于天玑多少?