Spring【AOP模組】知識要點
由於靜態代理需要實現目標物件的相同介面,那麼可能會導致代理類會非常非常多....不好維護---->因此出現了動態代理
動態代理也有個約束:目標物件一定是要有介面的,沒有介面就不能實現動態代理.....----->因此出現了cglib代理
cglib代理也叫子類代理,從記憶體中構建出一個子類來擴充套件目標物件的功能!
- [x] CGLIB是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件Java類與實現Java介面。它廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop,為他們提供方法的interception(攔截)。
接下來我們就講講怎麼寫cglib代理:
- 需要引入cglib – jar檔案, 但是spring的核心包中已經包括了cglib功能,所以直接引入spring-core-3.2.5.jar即可。
- 引入功能包後,就可以在記憶體中動態構建子類
- 代理的類不能為final,否則報錯【在記憶體中構建子類來做擴充套件,當然不能為final,有final就不能繼承了】
- 目標物件的方法如果為final/static, 那麼就不會被攔截,即不會執行目標物件額外的業務方法。
//需要實現MethodInterceptor介面
public class ProxyFactory implements MethodInterceptor{
// 維護目標物件
private Object target;
public ProxyFactory(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 obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("開始事務.....");
// 執行目標物件的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事務.....");
return returnValue;
}
}
- 測試:
public class App {
public static void main(String[] args) {
UserDao userDao = new UserDao();
UserDao factory = (UserDao) new ProxyFactory(userDao).getProxyInstance();
factory.save();
}
}
使用cglib就是為了彌補動態代理的不足【動態代理的目標物件一定要實現介面】
AOP 面向切面的程式設計:
- AOP可以實現“業務程式碼”與“關注點程式碼”分離
下面我們來看一段程式碼:
// 儲存一個使用者
public void add(User user) {
Session session = null;
Transaction trans = null;
try {
session = HibernateSessionFactoryUtils.getSession(); // 【關注點程式碼】
trans = session.beginTransaction(); // 【關注點程式碼】
session.save(user); // 核心業務程式碼
trans.commit(); //…【關注點程式碼】
} catch (Exception e) {
e.printStackTrace();
if(trans != null){
trans.rollback(); //..【關注點程式碼】
}
} finally{
HibernateSessionFactoryUtils.closeSession(session); ////..【關注點程式碼】
}
}
- 關注點程式碼,就是指重複執行的程式碼。
- 業務程式碼與關注點程式碼分離,好處?
- 關注點程式碼寫一次即可;
- 開發者只需要關注核心業務;
- 執行時期,執行核心業務程式碼時候動態植入關注點程式碼; 【代理】
- IUser介面
public interface IUser {
void save();
}
我們一步一步來分析,首先我們的UserDao有一個save()方法,每次都要開啟事務和關閉事務
//@Component -->任何地方都能用這個
@Repository //-->這個在Dao層中使用
public class UserDao {
public void save() {
System.out.println("開始事務");
System.out.println("DB:儲存使用者");
System.out.println("關閉事務");
}
}
- 在剛學習java基礎的時候,我們知道:如果某些功能經常需要用到就封裝成方法:
//@Component -->任何地方都能用這個
@Repository //-->這個在Dao層中使用
public class UserDao {
public void save() {
begin();
System.out.println("DB:儲存使用者");
close();
}
public void begin() {
System.out.println("開始事務");
}
public void close() {
System.out.println("關閉事務");
}
}
- 現在呢,我們可能有多個Dao,都需要有開啟事務和關閉事務的功能,現在只有UserDao中有這兩個方法,重用性還是不夠高。因此我們抽取出一個類出來
public class AOP {
public void begin() {
System.out.println("開始事務");
}
public void close() {
System.out.println("關閉事務");
}
}
- 在UserDao維護這個變數,要用的時候,呼叫方法就行了。
@Repository //-->這個在Dao層中使用
public class UserDao {
AOP aop;
public void save() {
aop.begin();
System.out.println("DB:儲存使用者");
aop.close();
}
}
- 現在的開啟事務、關閉事務還是需要我在userDao中手動呼叫。還是不夠優雅。。我想要的效果:當我在呼叫userDao的save()方法時,動態地開啟事務、關閉事務。因此,我們就用到了代理。當然了,真正執行方法的都是userDao、要幹事的是AOP,因此在代理中需要維護他們的引用。
public class ProxyFactory {
//維護目標物件
private static Object target;
//維護關鍵點程式碼的類
private static AOP aop;
public static Object getProxyInstance(Object target_, AOP aop_) {
//目標物件和關鍵點程式碼的類都是透過外界傳遞進來
target = target_;
aop = aop_;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
aop.begin();
Object returnValue = method.invoke(target, args);
aop.close();
return returnValue;
}
}
);
}
}
- 把AOP加入IOC容器中
//把該物件加入到容器中
@Component
public class AOP {
public void begin() {
System.out.println("開始事務");
}
public void close() {
System.out.println("關閉事務");
}
}
- 把UserDao放入容器中
@Component
public class UserDao {
public void save() {
System.out.println("DB:儲存使用者");
}
}
- 在配置檔案中開啟註解掃描,使用工廠靜態方法建立代理物件
- 測試,得到UserDao物件,呼叫方法
public class App {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
IUser iUser = (IUser) ac.getBean("proxy");
iUser.save();
}
}
上面使用的是工廠靜態方法來建立代理類物件。我們也使用一下非靜態的工廠方法建立物件。
package aa;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by ozc on 2017/5/11.
*/
public class ProxyFactory {
public Object getProxyInstance(final Object target_, final AOP aop_) {
//目標物件和關鍵點程式碼的類都是透過外界傳遞進來
return Proxy.newProxyInstance(
target_.getClass().getClassLoader(),
target_.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
aop_.begin();
Object returnValue = method.invoke(target_, args);
aop_.close();
return returnValue;
}
}
);
}
}
配置檔案:先建立工廠,再建立代理類物件
<!--建立工廠--> <!--透過工廠建立代理-->
Aop: aspect object programming 面向切面程式設計
- 功能: 讓關注點程式碼與業務程式碼分離!
- 面向切面程式設計就是指: 對很多功能都有的重複的程式碼抽取,再在執行的時候往業務方法上動態植入“切面類程式碼”。
關注點:
- 重複程式碼就叫做關注點。
// 儲存一個使用者
public void add(User user) {
Session session = null;
Transaction trans = null;
try {
session = HibernateSessionFactoryUtils.getSession(); // 【關注點程式碼】
trans = session.beginTransaction(); // 【關注點程式碼】
session.save(user); // 核心業務程式碼
trans.commit(); //…【關注點程式碼】
} catch (Exception e) {
e.printStackTrace();
if(trans != null){
trans.rollback(); //..【關注點程式碼】
}
} finally{
HibernateSessionFactoryUtils.closeSession(session); ////..【關注點程式碼】
}
}
切面:
- 關注點形成的類,就叫切面(類)!
public class AOP {
public void begin() {
System.out.println("開始事務");
}
public void close() {
System.out.println("關閉事務");
}
}
切入點:
- 執行目標物件方法,動態植入切面程式碼。
- 可以透過切入點表示式,指定攔截哪些類的哪些方法; 給指定的類在執行的時候植入切面類程式碼。
切入點表示式:
- 指定哪些類的哪些方法被攔截
1) 先引入aop相關jar檔案 (aspectj aop優秀元件)
- spring-aop-3.2.5.RELEASE.jar 【spring3.2原始碼】
- aopalliance.jar 【spring2.5原始碼/lib/aopalliance】
- aspectjweaver.jar 【spring2.5原始碼/lib/aspectj】或【aspectj-1.8.2lib】
- aspectjrt.jar 【spring2.5原始碼/lib/aspectj】或【aspectj-1.8.2lib】
注意: 用到spring2.5版本的jar檔案,如果用jdk1.7可能會有問題。
- 需要升級aspectj元件,即使用aspectj-1.8.2版本中提供jar檔案提供。
2) bean.xml中引入aop名稱空間
xmlns:context=""
/spring-context.xsd
引入4個jar包:
我們之前手動的實現AOP程式設計是需要自己來編寫代理工廠的,現在有了Spring,就不需要我們自己寫代理工廠了。Spring內部會幫我們建立代理工廠。
- 也就是說,不用我們自己寫代理物件了。
因此,我們只要關心切面類、切入點、編寫切入表示式指定攔截什麼方法就可以了!
還是以上一個例子為案例,使用Spring的註解方式來實現AOP程式設計
<!-- 開啟aop註解方式 -->
- 切面類
@Component
@Aspect//指定為切面類
public class AOP {
//裡面的值為切入點表示式
@Before("execution(* aa.*.*(..))")
public void begin() {
System.out.println("開始事務");
}
@After("execution(* aa.*.*(..))")
public void close() {
System.out.println("關閉事務");
}
}
- UserDao實現了IUser介面
@Component
public class UserDao implements IUser {
@Override
public void save() {
System.out.println("DB:儲存使用者");
}
}
- IUser介面
public interface IUser {
void save();
}
- 測試程式碼:
public class App {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
//這裡得到的是代理物件....
IUser iUser = (IUser) ac.getBean("userDao");
System.out.println(iUser.getClass());
iUser.save();
}
}
上面我們測試的是UserDao有IUser介面,內部使用的是動態代理...那麼我們這次測試的是目標物件沒有介面
- OrderDao沒有實現介面
@Component
public class OrderDao {
public void save() {
System.out.println("我已經進貨了!!!");
}
}
- 測試程式碼:
public class App {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
OrderDao orderDao = (OrderDao) ac.getBean("orderDao");
System.out.println(orderDao.getClass());
orderDao.save();
}
}
API:
- @Aspect 指定一個類為切面類
- *@Pointcut("execution( cn.itcast.e_aop_anno..(..))") 指定切入點表示式**
- @Before("pointCut_()") 前置通知: 目標方法之前執行
- @After("pointCut_()") 後置通知:目標方法之後執行(始終執行)
- @AfterReturning("pointCut_()") 返回後通知: 執行方法結束前執行(異常不執行)
- @AfterThrowing("pointCut_()") 異常通知: 出現異常時候執行
-
@Around("pointCut_()") 環繞通知: 環繞目標方法執行
- 測試:
// 前置通知 : 在執行目標方法之前執行
@Before("pointCut_()")
public void begin(){
System.out.println("開始事務/異常");
}
// 後置/最終通知:在執行目標方法之後執行 【無論是否出現異常最終都會執行】
@After("pointCut_()")
public void after(){
System.out.println("提交事務/關閉");
}
// 返回後通知: 在呼叫目標方法結束後執行 【出現異常不執行】
@AfterReturning("pointCut_()")
public void afterReturning() {
System.out.println("afterReturning()");
}
// 異常通知: 當目標方法執行異常時候執行此關注點程式碼
@AfterThrowing("pointCut_()")
public void afterThrowing(){
System.out.println("afterThrowing()");
}
// 環繞通知:環繞目標方式執行
@Around("pointCut_()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("環繞前....");
pjp.proceed(); // 執行目標方法
System.out.println("環繞後....");
}
最佳化
我們的程式碼是這樣的:每次寫Before、After等,都要重寫一次切入點表示式,這樣就不優雅了。
@Before("execution(* aa.*.*(..))")
public void begin() {
System.out.println("開始事務");
}
@After("execution(* aa.*.*(..))")
public void close() {
System.out.println("關閉事務");
}
於是乎,我們要使用@Pointcut這個註解,來指定切入點表示式,在用到的地方中,直接引用就行了!
- 那麼我們的程式碼就可以改造成這樣了:
@Component
@Aspect//指定為切面類
public class AOP {
// 指定切入點表示式,攔截哪個類的哪些方法
@Pointcut("execution(* aa.*.*(..))")
public void pt() {
}
@Before("pt()")
public void begin() {
System.out.println("開始事務");
}
@After("pt()")
public void close() {
System.out.println("關閉事務");
}
}
首先,我們把所有的註解都去掉...
- XML檔案配置
<!--物件例項--> <!--切面類--> <!--AOP配置--><!--定義切入表示式,攔截哪些方法--> <!--指定切面類是哪個--><!--指定來攔截的時候執行切面類的哪些方法-->
- 測試:
public class App {
@Test
public void test1() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
OrderDao orderDao = (OrderDao) ac.getBean("orderDao");
System.out.println(orderDao.getClass());
orderDao.save();
}
@Test
public void test2() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
IUser userDao = (IUser) ac.getBean("userDao");
System.out.println(userDao.getClass());
userDao.save();
}
}
測試OrderDao
測試UserDao
切入點表示式
切入點表示式主要就是來配置攔截哪些類的哪些方法
..我們去文件中找找它的語法...
在文件中搜尋:execution(
那麼它的語法是這樣子的:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
符號講解:
- ?號代表0或1,可以不寫
- *“”號代表任意型別,0或多**
- 方法引數為..表示為可變引數
引數講解:
- modifiers-pattern?【修飾的型別,可以不寫】
- ret-type-pattern【方法返回值型別,必寫】
- declaring-type-pattern?【方法宣告的型別,可以不寫】
- name-pattern(param-pattern)【要匹配的名稱,括號裡面是方法的引數】
- throws-pattern?【方法丟擲的異常型別,可以不寫】
官方也有給出一些例子給我們理解:
<!-- 【攔截所有public方法】 -->
<!-- -->
<!-- 【攔截所有save開頭的方法 】 -->
<!-- -->
<!-- 【攔截指定類的指定方法, 攔截時候一定要定位到方法】 -->
<!-- -->
<!-- 【攔截指定類的所有方法】 -->
<!-- -->
<!-- 【攔截指定包,以及其自包下所有類的所有方法】 -->
<!-- -->
<!-- 【多個表示式】 -->
<!-- -->
<!-- -->
<!-- 下面2個且關係的,沒有意義 -->
<!-- -->
<!-- -->
<!-- 【取非值】 -->
<!-- -->
相關文章
- MongoDB知識要點MongoDB
- spring知識點概述Spring
- Hibernate【快取】知識要點快取
- Hibernate【對映】知識要點
- 索引的知識要點與操作索引
- Spring知識點詳解Spring
- Spring知識點總結Spring
- Spring 冷知識:一個提前 AOP 的機會Spring
- linux-gcc簡要知識點 **LinuxGC
- Mybatis【逆向工程,快取,代理】知識要點MyBatis快取
- 44個路由器知識要點(轉)路由器
- spring-framework的Resource知識點SpringFramework
- Spring【AOP模組】就是這麼簡單Spring
- 【學習】MySQL基礎知識要點-001MySql
- Spring入門系列:淺析知識點Spring
- Spring IOC知識點一網打盡!Spring
- 學習運維技術要掌握哪些知識點?運維
- Hibernate【inverse和cascade屬性】知識要點
- 電腦“前端匯流排”的基本知識要點前端
- 深入理解微服務架構spring的各個知識點(面試必問知識點)微服務架構Spring面試
- iOS推流器模組知識點淺談總結iOS
- 知識點
- 做資料分析要掌握哪些軟體和知識點?
- 測試模組知識 Tree
- 精選Spring Boot三十五道必知必會知識點!Spring Boot
- Spring小知識Spring
- Spring和Springboot相關知識點整理Spring Boot
- 知識點-Spring Boot 異常處理彙總Spring Boot
- linux知識知識點總結Linux
- Promise知識點Promise
- 面試知識點面試
- Laravel 知識點Laravel
- 通用知識點
- 前端知識點前端
- ajax知識點
- rabbitmq 知識點MQ
- Redis知識點Redis
- SQL知識點(+)SQL