Spring中有個非常重要的知識點,AOP,即面相切面編程,spring中提供的一些非常牛逼的功能都是通過aop實現的,比如下面這些大家比較熟悉的功能
- spring事務管理:@Transactional
- spring異步處理:@EnableAsync
- spring緩存技術的使用:@EnableCaching
- spring中各種攔截器:@EnableAspectJAutoProxy
大家想玩轉spring,成爲一名spring高手,aop是必須要掌握的,aop這塊東西比較多,我們將通過三四篇文章來詳解介紹這塊的內容,由淺入深,讓大家全面掌握這塊知識。
說的簡單點,spring中的aop就是依靠代理實現的各種功能,通過代理來對bean進行增強。
spring中的aop功能主要是通過2種代理來實現的
- jdk動態代理
- cglib代理
繼續向下之前,必須先看一下這篇文章:Spring系列第15篇:代理詳解(Java動態代理&cglib代理)?
spring aop中用到了更多的一些特性,上面這邊文章中沒有介紹到,所以通過本文來做一個補充,這2篇文章看過之後,再去看spring aop的源碼,理解起來會容易一些,這2篇算是最基礎的知識,所以一定要消化理解,不然aop那塊的原理你很難了解,會暈車,
jdk動態代理
特徵
- 只能爲接口創建代理對象
- 創建出來的代理都是java.lang.reflect.Proxy的子類
案例
案例源碼位置:
com.javacode2018.aop.demo1.JdkAopTest1
有2個接口
interface IService1 { void m1(); } interface IService2 { void m2(); }
下面的類實現了上面2個接口
public static class Service implements IService1, IService2 { @Override public void m1() { System.out.println("我是m1"); } @Override public void m2() { System.out.println("我是m2"); } }
下面通過jdk動態代理創建一個代理對象,實現上面定義的2個接口,將代理對象所有的請求轉發給Service去處理,需要在代理中統計2個接口中所有方法的耗時。
比較簡單,自定義一個InvocationHandler
public static class CostTimeInvocationHandler implements InvocationHandler { private Object target; public CostTimeInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startime = System.nanoTime(); Object result = method.invoke(this.target, args); //將請求轉發給target去處理 System.out.println(method + ",耗時(納秒):" + (System.nanoTime() - startime)); return result; } }
測試方法
{ Service target = new Service(); CostTimeInvocationHandler costTimeInvocationHandler = new CostTimeInvocationHandler(target); //創建代理對象 Object proxyObject = Proxy.newProxyInstance( target.getClass().getClassLoader(), new Class[]{IService1.class, IService2.class}, //創建的代理對象實現了2個接口 costTimeInvocationHandler); //判斷代理對象是否是Service類型的,肯定是false咯 System.out.println(String.format("proxyObject instanceof Service = %s", proxyObject instanceof Service)); //判斷代理對象是否是IService1類型的,肯定是true System.out.println(String.format("proxyObject instanceof IService1 = %s", proxyObject instanceof IService1)); //判斷代理對象是否是IService2類型的,肯定是true System.out.println(String.format("proxyObject instanceof IService2 = %s", proxyObject instanceof IService2)); //將代理轉換爲IService1類型 IService1 service1 = (IService1) proxyObject; //調用IService2的m1方法 service1.m1(); //將代理轉換爲IService2類型 IService2 service2 = (IService2) proxyObject; //調用IService2的m2方法 service2.m2(); //輸出代理的類型 System.out.println("代理對象的類型:" + proxyObject.getClass()); }
運行輸出
proxyObject instanceof Service = false proxyObject instanceof IService1 = true proxyObject instanceof IService2 = true 我是m1 public abstract void com.javacode2018.aop.demo1.JdkAopTest1$IService1.m1(),耗時(納秒):225600 我是m2 public abstract void com.javacode2018.aop.demo1.JdkAopTest1$IService2.m2(),耗時(納秒):36000 代理對象的類型:class com.javacode2018.aop.demo1.$Proxy0
m1方法和m2方法被CostTimeInvocationHandler#invoke給增強了,調用目標方法的過程中統計了耗時。
最後一行輸出可以看出代理對象的類型,類名中包含了$Proxy的字樣,所以以後注意,看到這種字樣的,基本上都是通過jdk動態代理創建的代理對象。
下面來說cglib代理的一些特殊案例。
cglib代理
cglib的特點
- cglib彌補了jdk動態代理的不足,jdk動態代理只能爲接口創建代理,而cglib非常強大,不管是接口還是類,都可以使用cglib來創建代理
- cglib創建代理的過程,相當於創建了一個新的類,可以通過cglib來配置這個新的類需要實現的接口,以及需要繼承的父類
- cglib可以爲類創建代理,但是這個類不能是final類型的,cglib爲類創建代理的過程,實際上爲通過繼承來實現的,相當於給需要被代理的類創建了一個子類,然後會重寫父類中的方法,來進行增強,繼承的特性大家應該都知道,final修飾的類是不能被繼承的,final修飾的方法不能被重寫,static修飾的方法也不能被重寫,private修飾的方法也不能被子類重寫,而其他類型的方法都可以被子類重寫,被重寫的這些方法可以通過cglib進行攔截增強
cglib整個過程如下
- Cglib根據父類,Callback, Filter 及一些相關信息生成key
- 然後根據key 生成對應的子類的二進制表現形式
- 使用ClassLoader裝載對應的二進制,生成Class對象,並緩存
- 最後實例化Class對象,並緩存
案例1:爲多個接口創建代理
代碼比較簡單,定義了2個接口,然後通過cglib來創建一個代理類,代理類會實現這2個接口,通過setCallback來對2個接口的方法進行增強。
public class CglibTest1 { interface IService1 { void m1(); } interface IService2 { void m2(); } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); //設置代理對象需要實現的接口 enhancer.setInterfaces(new Class[]{IService1.class, IService2.class}); //通過Callback來對被代理方法進行增強 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("方法:" + method.getName()); return null; } }); Object proxy = enhancer.create(); if (proxy instanceof IService1) { ((IService1) proxy).m1(); } if (proxy instanceof IService2) { ((IService2) proxy).m2(); } //看一下代理對象的類型 System.out.println(proxy.getClass()); //看一下代理類實現的接口 System.out.println("創建代理類實現的接口如下:"); for (Class<?> cs : proxy.getClass().getInterfaces()) { System.out.println(cs); } } }
運行輸出
方法:m1 方法:m2 class com.javacode2018.aop.demo2.CglibTest1$IService1$$EnhancerByCGLIB$$1d32a82 創建代理類實現的接口如下: interface com.javacode2018.aop.demo2.CglibTest1$IService1 interface com.javacode2018.aop.demo2.CglibTest1$IService2 interface org.springframework.cglib.proxy.Factory
上面創建的代理類相當於下面代碼
public class CglibTest1$IService1$$EnhancerByCGLIB$$1d32a82 implements IService1, IService2 { @Override public void m1() { System.out.println("方法:m1"); } @Override public void m2() { System.out.println("方法:m2"); } }
案例2:爲類和接口同時創建代理
下面定義了2個接口:IService1和IService2,2個接口有個實現類:Service,然後通過cglib創建了個代理類,實現了這2個接口,並且將Service類作爲代理類的父類。
public class CglibTest2 { interface IService1 { void m1(); } interface IService2 { void m2(); } public static class Service implements IService1, IService2 { @Override public void m1() { System.out.println("m1"); } @Override public void m2() { System.out.println("m2"); } } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); //設置代理類的父類 enhancer.setSuperclass(Service.class); //設置代理對象需要實現的接口 enhancer.setInterfaces(new Class[]{IService1.class, IService2.class}); //通過Callback來對被代理方法進行增強 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { long startime = System.nanoTime(); Object result = methodProxy.invokeSuper(o, objects); //調用父類中的方法 System.out.println(method + ",耗時(納秒):" + (System.nanoTime() - startime)); return result; } }); //創建代理對象 Object proxy = enhancer.create(); //判斷代理對象是否是Service類型的 System.out.println("proxy instanceof Service" + (proxy instanceof Service)); if (proxy instanceof Service) { Service service = (Service) proxy; service.m1(); service.m2(); } //看一下代理對象的類型 System.out.println(proxy.getClass()); //輸出代理對象的父類 System.out.println("代理類的父類:" + proxy.getClass().getSuperclass()); //看一下代理類實現的接口 System.out.println("創建代理類實現的接口如下:"); for (Class<?> cs : proxy.getClass().getInterfaces()) { System.out.println(cs); } } }
運行輸出
proxy instanceof Servicetrue m1 public void com.javacode2018.aop.demo2.CglibTest2$Service.m1(),耗時(納秒):14219700 m2 public void com.javacode2018.aop.demo2.CglibTest2$Service.m2(),耗時(納秒):62800 class com.javacode2018.aop.demo2.CglibTest2$Service$$EnhancerByCGLIB$$80494536 代理類的父類:class com.javacode2018.aop.demo2.CglibTest2$Service 創建代理類實現的接口如下: interface com.javacode2018.aop.demo2.CglibTest2$IService1 interface com.javacode2018.aop.demo2.CglibTest2$IService2 interface org.springframework.cglib.proxy.Factory
輸出中可以代理對象的類型是:
class com.javacode2018.aop.demo2.CglibTest2$Service$$EnhancerByCGLIB$$80494536
帶有$$EnhancerByCGLIB$$字樣的,在調試spring的過程中,發現有這樣字樣的,基本上都是cglib創建的代理對象。
上面創建的代理類相當於下面代碼
public class CglibTest2$Service$$EnhancerByCGLIB$$80494536 extends Service implements IService1, IService2 { @Override public void m1() { long starttime = System.nanoTime(); super.m1(); System.out.println("方法m1,耗時(納秒):" + (System.nanoTime() - starttime)); } @Override public void m2() { long starttime = System.nanoTime(); super.m1(); System.out.println("方法m1,耗時(納秒):" + (System.nanoTime() - starttime)); } }
案例3:LazyLoader的使用
LazyLoader是cglib用於實現懶加載的callback。當被增強bean的方法初次被調用時,會觸發回調,之後每次再進行方法調用都是對LazyLoader第一次返回的bean調用,hibernate延遲加載有用到過這個。
看案例吧,通過案例理解容易一些。
public class LazyLoaderTest1 { public static class UserModel { private String name; public UserModel() { } public UserModel(String name) { this.name = name; } public void say() { System.out.println("你好:" + name); } } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserModel.class); //創建一個LazyLoader對象 LazyLoader lazyLoader = new LazyLoader() { @Override public Object loadObject() throws Exception { System.out.println("調用LazyLoader.loadObject()方法"); return new UserModel("路人甲java"); } }; enhancer.setCallback(lazyLoader); Object proxy = enhancer.create(); UserModel userModel = (UserModel) proxy; System.out.println("第1次調用say方法"); userModel.say(); System.out.println("第1次調用say方法"); userModel.say(); } }
運行輸出
第1次調用say方法 調用LazyLoader.loadObject()方法 你好:路人甲java 第1次調用say方法 你好:路人甲java
當第1次調用say方法的時候,會被cglib攔截,進入lazyLoader的loadObject內部,將這個方法的返回值作爲say方法的調用者,loadObject中返回了一個路人甲Java的UserModel,cglib內部會將loadObject方法的返回值和say方法關聯起來,然後緩存起來,而第2次調用say方法的時候,通過方法名去緩存中找,會直接拿到第1次返回的UserModel,所以第2次不會進入到loadObject方法中了。
將下代碼拆分開來
System.out.println("第1次調用say方法"); userModel.say(); System.out.println("第1次調用say方法"); userModel.say();
相當於下面的代碼
System.out.println("第1次調用say方法"); System.out.println("調用LazyLoader.loadObject()方法"); userModel = new UserModel("路人甲java"); userModel.say(); System.out.println("第1次調用say方法"); userModel.say();
下面通過LazyLoader實現延遲加載的效果。
案例4:LazyLoader實現延遲加載
博客的內容一般比較多,需要用到內容的時候,我們再去加載,下面來模擬博客內容延遲加載的效果。
public class LazyLoaderTest2 { //博客信息 public static class BlogModel { private String title; //博客內容信息比較多,需要的時候再去獲取 private BlogContentModel blogContentModel; public BlogModel() { this.title = "spring aop詳解!"; this.blogContentModel = this.getBlogContentModel(); } private BlogContentModel getBlogContentModel() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(BlogContentModel.class); enhancer.setCallback(new LazyLoader() { @Override public Object loadObject() throws Exception { //此處模擬從數據庫中獲取博客內容 System.out.println("開始從數據庫中獲取博客內容....."); BlogContentModel result = new BlogContentModel(); result.setContent("歡迎大家和我一起學些spring,我們一起成爲spring高手!"); return result; } }); return (BlogContentModel) enhancer.create(); } } //表示博客內容信息 public static class BlogContentModel { //博客內容 private String content; public String getContent() { return content; } public void setContent(String content) { this.content = content; } } public static void main(String[] args) { //創建博客對象 BlogModel blogModel = new BlogModel(); System.out.println(blogModel.title); System.out.println("博客內容"); System.out.println(blogModel.blogContentModel.getContent()); //@1 } }
@1:調用blogContentModel.getContent()方法的時候,纔會通過LazyLoader#loadObject方法從db中獲取到博客內容信息
運行輸出
spring aop詳解! 博客內容 開始從數據庫中獲取博客內容..... 歡迎大家和我一起學些spring,我們一起成爲spring高手!
案例5:Dispatcher
Dispatcher和LazyLoader作用很相似,區別是用Dispatcher的話每次對增強bean進行方法調用都會觸發回調。
看案例代碼
public class DispatcherTest1 { public static class UserModel { private String name; public UserModel() { } public UserModel(String name) { this.name = name; } public void say() { System.out.println("你好:" + name); } } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(LazyLoaderTest1.UserModel.class); //創建一個Dispatcher對象 Dispatcher dispatcher = new Dispatcher() { @Override public Object loadObject() throws Exception { System.out.println("調用Dispatcher.loadObject()方法"); return new LazyLoaderTest1.UserModel("路人甲java," + UUID.randomUUID().toString()); } }; enhancer.setCallback(dispatcher); Object proxy = enhancer.create(); LazyLoaderTest1.UserModel userModel = (LazyLoaderTest1.UserModel) proxy; System.out.println("第1次調用say方法"); userModel.say(); System.out.println("第1次調用say方法"); userModel.say(); } }
運行輸出
第1次調用say方法 調用Dispatcher.loadObject()方法 你好:路人甲java,514f911e-06ac-4e3b-aee4-595f82c16a5f 第1次調用say方法 調用Dispatcher.loadObject()方法 你好:路人甲java,bc062990-bc16-4226-97e3-b1b321a03468
案例6:通過Dispathcer對類擴展一些接口
下面有個UserService類,我們需要對這個類創建一個代理。
代碼中還定義了一個接口:IMethodInfo,用來統計被代理類的一些方法信息,有個實現類:DefaultMethodInfo。
通過cglib創建一個代理類,父類爲UserService,並且實現IMethodInfo接口,將接口IMethodInfo所有方法的轉發給DefaultMethodInfo處理,代理類中的其他方法,轉發給其父類UserService處理。
這個代碼相當於對UserService這個類進行了增強,使其具有了IMethodInfo接口中的功能。
public class DispatcherTest2 { public static class UserService { public void add() { System.out.println("新增用戶"); } public void update() { System.out.println("更新用戶信息"); } } //用來獲取方法信息的接口 public interface IMethodInfo { //獲取方法數量 int methodCount(); //獲取被代理的對象中方法名稱列表 List<String> methodNames(); } //IMethodInfo的默認實現 public static class DefaultMethodInfo implements IMethodInfo { private Class<?> targetClass; public DefaultMethodInfo(Class<?> targetClass) { this.targetClass = targetClass; } @Override public int methodCount() { return targetClass.getDeclaredMethods().length; } @Override public List<String> methodNames() { return Arrays.stream(targetClass.getDeclaredMethods()). map(Method::getName). collect(Collectors.toList()); } } public static void main(String[] args) { Class<?> targetClass = UserService.class; Enhancer enhancer = new Enhancer(); //設置代理的父類 enhancer.setSuperclass(targetClass); //設置代理需要實現的接口列表 enhancer.setInterfaces(new Class[]{IMethodInfo.class}); //創建一個方法統計器 IMethodInfo methodInfo = new DefaultMethodInfo(targetClass); //創建會調用器列表,此處定義了2個,第1個用於處理UserService中所有的方法,第2個用來處理IMethodInfo接口中的方法 Callback[] callbacks = { new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o, objects); } }, new Dispatcher() { @Override public Object loadObject() throws Exception { /** * 用來處理代理對象中IMethodInfo接口中的所有方法 * 所以此處返回的爲IMethodInfo類型的對象, * 將由這個對象來處理代理對象中IMethodInfo接口中的所有方法 */ return methodInfo; } } }; enhancer.setCallbacks(callbacks); enhancer.setCallbackFilter(new CallbackFilter() { @Override public int accept(Method method) { //當方法在IMethodInfo中定義的時候,返回callbacks中的第二個元素 return method.getDeclaringClass() == IMethodInfo.class ? 1 : 0; } }); Object proxy = enhancer.create(); //代理的父類是UserService UserService userService = (UserService) proxy; userService.add(); //代理實現了IMethodInfo接口 IMethodInfo mf = (IMethodInfo) proxy; System.out.println(mf.methodCount()); System.out.println(mf.methodNames()); } }
運行輸出
新增用戶 2 [add, update]
案例7:cglib中的NamingPolicy接口
接口NamingPolicy表示生成代理類的名字的策略,通過Enhancer.setNamingPolicy方法設置命名策略。
默認的實現類:DefaultNamingPolicy, 具體cglib動態生成類的命名控制。
DefaultNamingPolicy中有個getTag方法。
DefaultNamingPolicy生成的代理類的類名命名規則:
被代理class name + "$$" + 使用cglib處理的class name + "ByCGLIB" + "$$" + key的hashcode
如:
com.javacode2018.aop.demo2.DispatcherTest2$UserService$$EnhancerByCGLIB$$e7ec0be5@17d10166
自定義NamingPolicy,通常會繼承DefaultNamingPolicy來實現,spring中默認就提供了一個,如下
public class SpringNamingPolicy extends DefaultNamingPolicy { public static final SpringNamingPolicy INSTANCE = new SpringNamingPolicy(); @Override protected String getTag() { return "BySpringCGLIB"; } }
案例代碼
public class NamingPolicyTest { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(NamingPolicyTest.class); enhancer.setCallback(NoOp.INSTANCE); //通過Enhancer.setNamingPolicy來設置代理類的命名策略 enhancer.setNamingPolicy(new DefaultNamingPolicy() { @Override protected String getTag() { return "-test-"; } }); Object proxy = enhancer.create(); System.out.println(proxy.getClass()); } }
輸出
class com.javacode2018.aop.demo2.NamingPolicyTest$$Enhancer-test-$$5946713
Objenesis:實例化對象的一種方式
先來看一段代碼,有一個有參構造函數:
public static class User { private String name; public User(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } }
大家來思考一個問題:如果不使用這個有參構造函數的情況下,如何創建這個對象?
通過反射?大家可以試試,如果不使用有參構造函數,是無法創建對象的。
cglib中提供了一個接口:Objenesis,通過這個接口可以解決上面這種問題,它專門用來創建對象,即使你沒有空的構造函數,都木有問題,它不使用構造方法創建Java對象,所以即使你有空的構造方法,也是不會執行的。
用法比較簡單:
@Test public void test1() { Objenesis objenesis = new ObjenesisStd(); User user = objenesis.newInstance(User.class); System.out.println(user); }
輸出
User{name='null'}
大家可以在User類中加一個默認構造函數,來驗證一下上面的代碼會不會調用默認構造函數?
public User() { System.out.println("默認構造函數"); }
再次運行會發現,並不會調用默認構造函數。
如果需要多次創建User對象,可以寫成下面方式重複利用
@Test public void test2() { Objenesis objenesis = new ObjenesisStd(); ObjectInstantiator<User> userObjectInstantiator = objenesis.getInstantiatorOf(User.class); User user1 = userObjectInstantiator.newInstance(); System.out.println(user1); User user2 = userObjectInstantiator.newInstance(); System.out.println(user2); System.out.println(user1 == user2); }
運行輸出
User{name='null'} User{name='null'} false
代碼位置
com.javacode2018.aop.demo2.CreateObjectTest
總結
- 代理這2篇文章是spring aop的基礎,基礎牢靠了,才能走的更遠,大家一定要將這2篇文章中的內容吃透,全面掌握jdk動態代理和cglib代理的使用
- 這些知識點spring aop中全部都用到了,大家消化一下,下一篇講解spring aop具體是如何玩的
Spring作爲現在最流行的java 開發技術,其內部源碼設計非常優秀。如果你不會Spring,那麼很可能面試官會讓你回家等通知。
Spring是什麼?
有一個工地,幾百號人在用鐵鍬鏟子挖坑。
如果開一輛挖掘機來,用一天時間乾的活就相當於一個工人一個月的工作量。而且這個挖掘機是免費開源的,不用花錢買,僅僅需要學習掌握如何操作。
你會如何選擇?
這幾百號人的工地就是企業應用項目實施團隊,而挖掘機就是Spring。
Spring框架爲開發Java應用程序提供了全面的基礎架構支持。Spring包含了一些很好的功能,如依賴注入和開箱即用的模塊:
Spring JDBC
Spring MVC
Spring Security
Spring AOP
Spring ORM
Spring Test
這些模塊能極大縮短應用程序的開發時間,提高我們的工作效率。
Spring底層到底要看什麼?以下是大神整理的學習筆記,給大家分享一下,希望可以對你掌握Spring有所幫助。(xmind格式可在文末獲取)
Spring學習筆記(完整內容在xmind文件中)
但是現在很多程序員對於Spring的理解只停留在很淺的層面。很多人只關注自己用的那部分代碼的邏輯,而並不真正去理解框架。
如果你不懂Spring,那麼大廠面試官也不會懂你爲什麼敢來面試?
看騰訊技術大牛帶你玩轉Spring全家桶,贈三本Spring實戰篇電子文檔
背景介紹
毋庸置疑,Spring 早已成爲 Java 後端開發事實上的行業標準,無數的公司選擇 Spring 作爲基礎的開發框架,大部分 Java 後端程序員在日常工作中也會接觸到Spring ,因此,如何用好 Spring ,也就成爲 Java程序員的必修課之一。
同時,Spring Boot 和 Spring Cloud的出現,可以幫助工程師更好地基於 Spring 及各種基礎設施來快速搭建系統,可以說,它們的誕生又一次解放了大家的生產力。
因此,Spring Boot 和 Spring Cloud已成爲 Spring 生態中不可或缺的一環。想成爲一名合格的Java 後端工程師,Spring Framework、Spring Boot、Spring Cloud 這三者必須都牢牢掌握。
今天樓主就給大家分享Spring,Spring Boot,Spring Cloud的電子書學習資料!
轉發文章並關注樓主,然後私信我回復【架構書籍】領取Spring全家桶實戰文檔
內容目錄
【Spring實戰】
【深入實踐Spring Boot2.x】
【Spring Cloud微服務實戰】
轉發文章並關注樓主,然後私信我【架構書籍】即可免費領取Spring全家桶實戰文檔
資料真實有效,絕不弄虛作假!