Spring 的 getBean 方法源碼解析spring
AOP 既熟悉又陌生,瞭解過 Spring 人的都知道 AOP 的概念,即面向切面編程,能夠用來管理一些和主業務無關的周邊業務,如日誌記錄,事務管理等;陌生是由於在工做中基本沒有使用過,AOP 的相關概念也是雲裏霧裏;最近在看 Spring 的相關源碼,因此仍是先來捋一捋 Spring 中 AOP 的一個用法。編程
在學習 Spring AOP 的用法以前,先來看看 AOP 的相關概念,ide
Spring AOP 的詳細介紹,請參考官網 Aspect Oriented Programming with Spring學習
1. Join point :鏈接點,表示程序執行期間的一個點,在 Spring AOP 表示的就是一個方法,即一個方法能夠看做是一個 Join point測試
2. pointcut :切點,就是與鏈接點匹配的謂詞,什麼意思呢,就是須要執行 Advice 的鏈接點就是切點this
3. Advice :加強,在鏈接點執行的操做,分爲前置、後置、異常、最終、環繞加強五種spa
4. Aspect :切面,由 pointcut 和 Advice 組成,能夠簡單的認爲 @Aspect 註解的類就是一個切面.net
5. Target object :目標對象,即 織入 advice 的目標對象
6. AOP proxy :代理類,在 Spring AOP 中, 一個 AOP 代理是一個 JDK 動態代理對象或 CGLIB 代理對象
7. Weaving :織入,將 Aspect 應用到目標對象中去
注:上述幾個概念中,比較容易混淆的是 Join point 和 pointcut,能夠這麼來理解,在 Spring AOP 中,全部的可執行方法都是 Join point,全部的 Join point 均可以植入 Advice;而 pointcut 能夠看做是一種描述信息,它修飾的是 Join point,用來確認在哪些 Join point 上執行 Advice,
在瞭解了 AOP 的概念以後,接下來就來看看如何使用 Spring Aop
1. 要想使用 Spring AOP ,首先先得在 Spring 配置文件中配置以下標籤:
<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>
該標籤有兩個屬性, expose-proxy 和 proxy-target-class ,默認值都爲 false;
expose-proxy :是否須要將當前的代理對象使用 ThreadLocal 進行保存,這是什麼意思呢,例如 Aop 須要對某個接口下的全部方法進行攔截,可是有些方法在內部進行自我調用,以下所示:
public void test_1() { this.test_2(); } public void test_2() { }
調用 test_1,此時 test_2 將不會被攔截進行加強,由於調用的是 AOP 代理對象而不是當前對象,而 在 test_1 方法內部使用的是 this 進行調用,因此 test_2 將不會被攔截加強,因此該屬性 expose-proxy 就是用來解決這個問題的,即 AOP 代理的獲取。
proxy-target-class :是否使用 CGLIB 進行代理,由於 Spring AOP 的底層技術就是使用的是動態代理,分爲 JDK 代理 和 CGLIB 代理,該屬性的默認值爲 false,表示 AOP 底層默認使用的使用 JDK 代理,當須要代理的類沒有實現任何接口的時候纔會使用 CGLIB 進行代理,若是想都是用 CGLIB 進行代理,能夠把該屬性設置爲 true 便可。
2. 定義須要 aop 攔截的方法,模擬一個 User 的增刪改操做:
接口:
public interface IUserService { void add(User user); User query(String name); List<User> qyertAll(); void delete(String name); void update(User user); }
@Service("userServiceImpl") public class UserServiceImpl implements IUserService { @Override public void add(User user) { System.out.println("添加用戶成功,user=" + user); } @Override public User query(String name) { System.out.println("根據name查詢用戶成功"); User user = new User(name, 20, 1, 1000, "java"); return user; } @Override public List<User> qyertAll() { List<User> users = new ArrayList<>(2); users.add(new User("zhangsan", 20, 1, 1000, "java")); users.add(new User("lisi", 25, 0, 2000, "Python")); System.out.println("查詢全部用戶成功, users = " + users); return users; } @Override public void delete(String name) { System.out.println("根據name刪除用戶成功, name = " + name); } @Override public void update(User user) { System.out.println("更新用戶成功, user = " + user); } }
3. 定義 AOP 切面
在 Spring AOP 中,使用 @Aspect 註解標識的類就是一個切面,而後在切面中定義切點(pointcut)和 加強(advice):
3.1 前置加強,@Before(),在目標方法執行以前執行
@Component @Aspect public class UserAspectj { // 在方法執行以前執行 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") public void before_1(){ System.out.println("log: 在 add 方法以前執行...."); } }
上述的方法 before_1() 是對接口的 add() 方法進行 前置加強,即在 add() 方法執行以前執行,
測試:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("/resources/myspring.xml") public class TestBean { @Autowired private IUserService userServiceImpl; @Test public void testAdd() { User user = new User("zhangsan", 20, 1, 1000, "java"); userServiceImpl.add(user); } } // 結果: // log: 在 add 方法以前執行.... // 添加用戶成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
若是想要獲取目標方法執行的參數等信息呢,咱們可在 切點的方法中添參數 JoinPoint ,經過它了獲取目標對象的相關信息:
@Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") public void before_2(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); User user = null; if(args[0].getClass() == User.class){ user = (User) args[0]; } System.out.println("log: 在 add 方法以前執行, 方法參數 = " + user); }
從新執行上述測試代碼,結果以下:
// log: 在 add 方法以前執行, 方法參數 = User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'} // 添加用戶成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
3.2 後置加強,@After(),在目標方法執行以後執行,不管是正常退出仍是拋異常,都會執行
// 在方法執行以後執行 @After("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") public void after_1(){ System.out.println("log: 在 add 方法以後執行...."); }
執行 3.1 的測試代碼,結果以下:
// 添加用戶成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'} // log: ==== 方法執行以後 =====
3.3 返回加強,@AfterReturning(),在目標方法正常返回後執行,出現異常則不會執行,能夠獲取到返回值:
@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object") public void after_return(Object object){ System.out.println("在 query 方法返回後執行, 返回值= " + object); }
測試:
@Test public void testQuery() { userServiceImpl.query("zhangsan"); } // 結果: // 根據name查詢用戶成功 // 在 query 方法返回後執行, 返回值= User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
當一個方法同時被 @After() 和 @AfterReturning() 加強的時候,先執行哪個呢?
@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object") public void after_return(Object object){ System.out.println("===log: 在 query 方法返回後執行, 返回值= " + object); } @After("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))") public void after_2(){ System.out.println("===log: 在 query 方法以後執行...."); }
測試:
// 根據name查詢用戶成功 // ===log: 在 query 方法以後執行.... // ===log: 在 query 方法返回後執行, 返回值= User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
能夠看到,即便 @After() 放在 @AfterReturning() 的後面,它也先被執行,即 @After() 在 @AfterReturning() 以前執行。
3.4 異常加強,@AfterThrowing,在拋出異常的時候執行,不拋異常不執行。
@AfterThrowing(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", throwing = "ex") public void after_throw(Exception ex){ System.out.println("在 query 方法拋異常時執行, 異常= " + ex); }
如今來修改一下它加強的 query() 方法,讓它拋出異常:
@Override public User query(String name) { System.out.println("根據name查詢用戶成功"); User user = new User(name, 20, 1, 1000, "java"); int a = 1/0; return user; }
測試:
@Test public void testQuery() { userServiceImpl.query("zhangsan"); } // 結果: // 在 query 方法拋異常時執行, 異常= java.lang.ArithmeticException: / by zero // java.lang.ArithmeticException: / by zero ...........
3.5 環繞加強,@Around,在目標方法執行以前和以後執行
// 目標方法: @Override public void update(User user) { System.out.println("更新用戶成功, user = " + user); } @Around("execution(* main.tsmyk.mybeans.inf.IUserService.delete(..))") public void test_around(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); System.out.println("log : delete 方法執行以前, 參數 = " + args[0].toString()); joinPoint.proceed(); System.out.println("log : delete 方法執行以後"); }
測試:
@Test public void test5(){ userServiceImpl.delete("zhangsan"); } // 結果: // log : delete 方法執行以前, 參數 = zhangsan // 根據name刪除用戶成功, name = zhangsan // log : delete 方法執行以後
以上就是 Spring AOP 的幾種加強。
上面的栗子中,在每一個方法上方的切點表達式都須要寫一遍,如今能夠使用 @Pointcut 來聲明一個可重用的切點表達式,以後在每一個方法的上方引用這個切點表達式便可。:
// 聲明 pointcut @Pointcut("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))") public void pointcut(){ } @Before("pointcut()") public void before_3(){ System.out.println("log: 在 query 方法以前執行"); } @After("pointcut()") public void after_4(){ System.out.println("log: 在 query 方法以後執行...."); }
在上面的栗子中,使用了 execution 指示符,它用來匹配方法執行的鏈接點,也是 Spring AOP 使用的主要指示符,在切點表達式中使用了 通配符 (*) 和 (.. ),其中,(* )能夠表示任意方法,任意返回值,(..)表示方法的任意參數 ,接下來來看下其餘的指示符。
匹配特定包下的全部類的全部 Joinpoint(方法),包括子包,注意是全部類,而不是接口,若是寫的是接口,則不會生效,如 within(main.tsmyk.mybeans.impl.* 將會匹配 main.tsmyk.mybeans.impl 包下全部類的全部 Join point;within(main.tsmyk.mybeans.impl..* 兩個點將會匹配該包及其子包下的全部類的全部 Join point。
栗子:
@Pointcut("within(main.tsmyk.mybeans.impl.*)") public void testWithin(){ } @Before("testWithin()") public void test_within(){ System.out.println("test within 在方法執行以前執行....."); }
執行該包下的類 UserServiceImpl 的 delete 方法,結果以下:
@Test public void test5(){ userServiceImpl.delete("zhangsan"); } // 結果: // test within 在方法執行以前執行..... // 根據name刪除用戶成功, name = zhangsan
匹配全部持有指定註解類型的方法,如 @within(Secure),任何目標對象持有Secure註解的類方法;必須是在目標對象上聲明這個註解,在接口上聲明的對它不起做用。
匹配的是一個目標對象,target(main.tsmyk.mybeans.inf.IUserService) 匹配的是該接口下的全部 Join point :
@Pointcut("target(main.tsmyk.mybeans.inf.IUserService)") public void anyMethod(){ } @Before("anyMethod()") public void beforeAnyMethod(){ System.out.println("log: ==== 方法執行以前 ====="); } @After("anyMethod()") public void afterAnyMethod(){ System.out.println("log: ==== 方法執行以後 ====="); }
以後,執行該接口下的任意方法,都會被加強。
匹配一個目標對象,這個對象必須有特定的註解,如
@target(org.springframework.transaction.annotation.Transactional) 匹配任何 有 @Transactional 註解的 方法
匹配當前AOP代理對象類型的執行方法,this(service.IPointcutService),當前AOP對象實現了 IPointcutService接口的任何方法
匹配參數,
// 匹配只有一個參數 name 的方法 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name)") public void test_arg(){ } // 匹配第一個參數爲 name 的方法 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name, ..)") public void test_arg2(){ } // 匹配第二個參數爲 name 的方法 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(*, name, ..)") public void test_arg3(){ }
匹配參數,參數有特定的註解,@args(Anno)),方法參數標有Anno註解。
匹配特定註解
@annotation(org.springframework.transaction.annotation.Transactional) 匹配 任何帶有 @Transactional 註解的方法。
匹配特定的 bean 名稱的方法
// 匹配 bean 的名稱爲 userServiceImpl 的全部方法 @Before("bean(userServiceImpl)") public void test_bean(){ System.out.println("==================="); } // 匹配 bean 名稱以 ServiceImpl 結尾的全部方法 @Before("bean(*ServiceImpl)") public void test_bean2(){ System.out.println("+++++++++++++++++++"); }
測試:
執行該bean下的方法:
@Test public void test5(){ userServiceImpl.delete("zhangsan"); } //結果: // =================== // +++++++++++++++++++ // 根據name刪除用戶成功, name = zhangsan
以上就是 Spring AOP 全部的指示符的使用方法了。
Spring AOP 的底層使用的使用 動態代理;共有兩種方式來實現動態代理,一個是 JDK 的動態代理,一種是 CGLIB 的動態代理,下面使用這兩種方式來實現以上面的功能,即在調用 UserServiceImpl 類方法的時候,在方法執行以前和以後加上日誌。
實現 JDK 動態代理,必需要實現 InvocationHandler 接口,並重寫 invoke 方法:
public class UserServiceInvocationHandler implements InvocationHandler { // 代理的目標對象 private Object target; public UserServiceInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("log: 目標方法執行以前, 參數 = " + args); // 執行目標方法 Object retVal = method.invoke(target, args); System.out.println("log: 目標方法執行以後....."); return retVal; } }
測試:
public static void main(String[] args) throws IOException { // 須要代理的對象 IUserService userService = new UserServiceImpl(); InvocationHandler handler = new UserServiceInvocationHandler(userService); ClassLoader classLoader = userService.getClass().getClassLoader(); Class[] interfaces = userService.getClass().getInterfaces(); // 代理對象 IUserService proxyUserService = (IUserService) Proxy.newProxyInstance(classLoader, interfaces, handler); System.out.println("動態代理的類型 = " + proxyUserService.getClass().getName()); proxyUserService.query("zhangsan"); // 把字節碼寫到文件 byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{UserServiceImpl.class}); FileOutputStream fos =new FileOutputStream(new File("D:/$Proxy.class")); fos.write(bytes); fos.flush(); }
結果:
動態代理的類型 = com.sun.proxy.$Proxy0 log: 目標方法執行以前, 參數 = [Ljava.lang.Object;@2ff4acd0 根據name查詢用戶成功 log: 目標方法執行以後.....
能夠看到在執行目標方法的先後已經打印了日誌;剛在上面的 main 方法中,咱們把代理對象的字節碼寫到了文件裏,如今來分析下:
反編譯 &Proxy.class 文件以下:
能夠看到它經過實現接口來實現的。
JDK 只能代理那些實現了接口的類,若是一個類沒有實現接口,則沒法爲這些類建立代理。此時能夠使用 CGLIB 來進行代理。
接下來看下 CGLIB 是如何實現的。
首先新建一個須要代理的類,它沒有實現任何接口:
public class UserServiceImplCglib{ public User query(String name) { System.out.println("根據name查詢用戶成功, name = " + name); User user = new User(name, 20, 1, 1000, "java"); return user; } }
如今須要使用 CGLIB 來實如今方法 query 執行的先後加上日誌:
使用 CGLIB 來實現動態代理,也須要實現接口 MethodInterceptor,重寫 intercept 方法:
public class CglibMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("log: 目標方法執行以前, 參數 = " + args); Object retVal = methodProxy.invokeSuper(obj, args); System.out.println("log: 目標方法執行以後, 返回值 = " + retVal); return retVal; } }
測試:
public static void main(String[] args) { // 把代理類寫入到文件 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\"); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserServiceImplCglib.class); enhancer.setCallback(new CglibMethodInterceptor()); // 建立代理對象 UserServiceImplCglib userService = (UserServiceImplCglib) enhancer.create(); System.out.println("動態代理的類型 = " + userService.getClass().getName()); userService.query("zhangsan"); }
結果:
動態代理的類型 = main.tsmyk.mybeans.impl.UserServiceImplCglib$$EnhancerByCGLIB$$772edd85 log: 目標方法執行以前, 參數 = [Ljava.lang.Object;@77556fd 根據name查詢用戶成功, name = zhangsan log: 目標方法執行以後, 返回值 = User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
能夠看到,結果和使用 JDK 動態代理的同樣,此外,能夠看到代理類的類型爲 main.tsmyk.mybeans.impl.UserServiceImplCglib$$EnhancerByCGLIB$$772edd85,它是 UserServiceImplCglib 的一個子類,即 CGLIB 是經過 繼承的方式來實現的。
1. JDK 的動態代理是經過反射和攔截器的機制來實現的,它會爲代理的接口生成一個代理類。
2. CGLIB 的動態代理則是經過繼承的方式來實現的,把代理類的class文件加載進來,經過修改其字節碼生成子類的方式來處理。
3. JDK 動態代理只能對實現了接口的類生成代理,而不能針對類。
4. CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,可是由於採用的是繼承, 因此 final 類或方法沒法被代理。
5. Spring AOP 中,若是實現了接口,默認使用的是 JDK 代理,也能夠強制使用 CGLIB 代理,若是要代理的類沒有實現任何接口,則會使用 CGLIB 進行代理,Spring 會進行自動的切換。
上述實現 Spring AOP 的栗子採用的是 註解的方法來實現的,此外,還能夠經過配置文件的方式來實現 AOP 的功能。以上就是 Spring AOP 的一個詳細的使用過程。