您好,登錄后才能下訂單哦!
這篇文章主要介紹“Spring AOP標簽怎么使用”,在日常操作中,相信很多人在Spring AOP標簽怎么使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Spring AOP標簽怎么使用”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
1
有一個接口Dao有insert、delete、update三個方法,在insert與update被調用的前后,打印調用前的毫秒數與調用后的毫秒數
首先定義一個Dao接口:
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public interface Dao { public void insert(); public void delete(); public void update(); }
然后定義一個實現類DaoImpl:
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public class DaoImpl implements Dao { @Override public void insert() { System.out.println("DaoImpl.insert()"); } @Override public void delete() { System.out.println("DaoImpl.delete()"); } @Override public void update() { System.out.println("DaoImpl.update()"); } }
最原始的寫法,我要在調用insert()與update()方法前后分別打印時間,就只能定義一個新的類包一層,在調用insert()方法與update()方法前后分別處理一下,新的類我命名為ServiceImpl,其實現為:
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public class ServiceImpl { private Dao dao = new DaoImpl(); public void insert() { System.out.println("insert()方法開始時間:" + System.currentTimeMillis()); dao.insert(); System.out.println("insert()方法結束時間:" + System.currentTimeMillis()); } public void delete() { dao.delete(); } public void update() { System.out.println("update()方法開始時間:" + System.currentTimeMillis()); dao.update(); System.out.println("update()方法結束時間:" + System.currentTimeMillis()); } }
這是最原始的寫法,這種寫法的缺點也是一目了然:
方法調用前后輸出時間的邏輯無法復用,如果有別的地方要增加這段邏輯就得再寫一遍
如果Dao有其它實現類,那么必須新增一個類去包裝該實現類,這將導致類數量不斷膨脹
使用裝飾器模式
接著我們使用上設計模式,先用裝飾器模式,看看能解決多少問題。裝飾器模式的核心就是實現Dao接口并持有Dao接口的引用,我將新增的類命名為LogDao,其實現為:
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public class LogDao implements Dao { private Dao dao; public LogDao(Dao dao) { this.dao = dao; } @Override public void insert() { System.out.println("insert()方法開始時間:" + System.currentTimeMillis()); dao.insert(); System.out.println("insert()方法結束時間:" + System.currentTimeMillis()); } @Override public void delete() { dao.delete(); } @Override public void update() { System.out.println("update()方法開始時間:" + System.currentTimeMillis()); dao.update(); System.out.println("update()方法結束時間:" + System.currentTimeMillis()); } }
在使用的時候,可以使用”Dao dao = new LogDao(new DaoImpl())”的方式,這種方式的優點為:
透明,對調用方來說,它只知道Dao,而不知道加上了日志功能
類不會無限膨脹,如果Dao的其它實現類需要輸出日志,只需要向LogDao的構造函數中傳入不同的Dao實現類即可
不過這種方式同樣有明顯的缺點,缺點為:
輸出日志的邏輯還是無法復用
輸出日志的邏輯與代碼有耦合,如果我要對delete()方法前后同樣輸出時間,需要修改LogDao
但是,這種做法相比最原始的代碼寫法,已經有了很大的改進。
使用代理模式
接著我們使用代理模式嘗試去實現最原始的功能,使用代理模式,那么我們就要定義一個InvocationHandler,我將它命名為LogInvocationHandler,其實現為:
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public class LogInvocationHandler implements InvocationHandler { private Object obj; public LogInvocationHandler(Object obj) { this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if ("insert".equals(methodName) || "update".equals(methodName)) { System.out.println(methodName + "()方法開始時間:" + System.currentTimeMillis()); Object result = method.invoke(obj, args); System.out.println(methodName + "()方法結束時間:" + System.currentTimeMillis()); return result; } return method.invoke(obj, args); } }
其調用方式很簡單,我寫一個main函數:
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public static void main(String[] args) { Dao dao = new DaoImpl(); Dao proxyDao = (Dao)Proxy.newProxyInstance(LogInvocationHandler.class.getClassLoader(), new Class<?>[]{Dao.class}, new LogInvocationHandler(dao)); proxyDao.insert(); System.out.println("----------分割線----------"); proxyDao.delete(); System.out.println("----------分割線----------"); proxyDao.update(); }
結果就不演示了,這種方式的優點為:
輸出日志的邏輯被復用起來,如果要針對其他接口用上輸出日志的邏輯,只要在newProxyInstance的時候的第二個參數增加Class<?>數組中的內容即可
這種方式的缺點為:
JDK提供的動態代理只能針對接口做代理,不能針對類做代理
代碼依然有耦合,如果要對delete方法調用前后打印時間,得在LogInvocationHandler中增加delete方法的判斷
使用CGLIB
接著看一下使用CGLIB的方式,使用CGLIB只需要實現MethodInterceptor接口即可:
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public class DaoProxy implements MethodInterceptor { @Override public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable { String methodName = method.getName(); if ("insert".equals(methodName) || "update".equals(methodName)) { System.out.println(methodName + "()方法開始時間:" + System.currentTimeMillis()); proxy.invokeSuper(object, objects); System.out.println(methodName + "()方法結束時間:" + System.currentTimeMillis()); return object; } proxy.invokeSuper(object, objects); return object; } }
代碼調用方式為:
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public static void main(String[] args) { DaoProxy daoProxy = new DaoProxy(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(DaoImpl.class); enhancer.setCallback(daoProxy); Dao dao = (DaoImpl)enhancer.create(); dao.insert(); System.out.println("----------分割線----------"); dao.delete(); System.out.println("----------分割線----------"); dao.update(); }
使用CGLIB解決了JDK的Proxy無法針對類做代理的問題,但是這里要專門說明一個問題:使用裝飾器模式可以說是對使用原生代碼的一種改進,使用Java代理可以說是對于使用裝飾器模式的一種改進,但是使用CGLIB并不是對于使用Java代理的一種改進。
前面的可以說改進是因為使用裝飾器模式比使用原生代碼更好,使用Java代理又比使用裝飾器模式更好,但是Java代理與CGLIb的對比并不能說改進,因為使用CGLIB并不一定比使用Java代理更好,這兩種各有優缺點,像Spring框架就同時支持Java Proxy與CGLIB兩種方式。
從目前看來代碼又更好了一些,但是我認為還有兩個缺點:
無論使用Java代理還是使用CGLIB,編寫這部分代碼都稍顯麻煩
代碼之間的耦合還是沒有解決,像要針對delete()方法加上這部分邏輯就必須修改代碼
最后來看一下使用AOP的方式,首先定義一個時間處理類,我將它命名為TimeHandler:
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public class TimeHandler { public void printTime(ProceedingJoinPoint pjp) { Signature signature = pjp.getSignature(); if (signature instanceof MethodSignature) { MethodSignature methodSignature = (MethodSignature)signature; Method method = methodSignature.getMethod(); System.out.println(method.getName() + "()方法開始時間:" + System.currentTimeMillis()); try { pjp.proceed(); System.out.println(method.getName() + "()方法結束時間:" + System.currentTimeMillis()); } catch (Throwable e) { } } } }
到第8行的代碼與第12行的代碼分別打印方法開始執行時間與方法結束執行時間。我這里寫得稍微復雜點,使用了 aop:around的寫法,其實也可以拆分為 aop:before與 aop:after兩種,這個看個人喜好。
這里多說一句,切面方法printTime本身可以不用定義任何的參數,但是有些場景下需要獲取調用方法的類、方法簽名等信息,此時可以在printTime方法中定義JointPoint,Spring會自動將參數注入,可以通過JoinPoint獲取調用方法的類、方法簽名等信息。由于這里我用的 aop:around,要保證方法的調用,這樣才能在方法調用前后輸出時間,因此不能直接使用JoinPoint,因為JoinPoint沒法保證方法調用。此時可以使用ProceedingJoinPoint,ProceedingPointPoint的proceed()方法可以保證方法調用,但是要注意一點,ProceedingJoinPoint只能和 aop:around搭配,換句話說,如果aop.xml中配置的是 aop:before,然后printTime的方法參數又是ProceedingJoinPoint的話,Spring容器啟動將報錯。
接著看一下aop.xml的配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="daoImpl" class="org.xrq.spring.action.aop.DaoImpl" /> <bean id="timeHandler" class="org.xrq.spring.action.aop.TimeHandler" /> <aop:config> <aop:pointcut id="addAllMethod" expression="execution(* org.xrq.spring.action.aop.Dao.*(..))" /> <aop:aspect id="time" ref="timeHandler"> <aop:before method="printTime" pointcut-ref="addAllMethod" /> <aop:after method="printTime" pointcut-ref="addAllMethod" /> </aop:aspect> </aop:config> </beans>
我不大會寫expression,也懶得去百度了,因此這里就攔截Dao下的所有方法了。測試代碼很簡單:
=
/**
* @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html
*/
public class AopTest {
@Test @SuppressWarnings("resource") public void testAop() { ApplicationContext ac = new ClassPathXmlApplicationContext("spring/aop.xml"); Dao dao = (Dao)ac.getBean("daoImpl"); dao.insert(); System.out.println("----------分割線----------"); dao.delete(); System.out.println("----------分割線----------"); dao.update(); } }
結果就不演示了。到此我總結一下使用AOP的幾個優點:
切面的內容可以復用,比如TimeHandler的printTime方法,任何地方需要打印方法執行前的時間與方法執行后的時間,都可以使用TimeHandler的printTime方法
避免使用Proxy、CGLIB生成代理,這方面的工作全部框架去實現,開發者可以專注于切面內容本身
代碼與代碼之間沒有耦合,如果攔截的方法有變化修改配置文件即可
下面用一張圖來表示一下AOP的作用:
我們傳統的編程方式是垂直化的編程,即A–>B–>C–>D這么下去,一個邏輯完畢之后執行另外一段邏輯。但是AOP提供了另外一種思路,它的作用是在業務邏輯不知情(即業務邏輯不需要做任何的改動)的情況下對業務代碼的功能進行增強,這種編程思想的使用場景有很多,例如事務提交、方法執行之前的權限檢測、日志打印、方法調用事件等等。
AOP使用場景舉例
上面的例子純粹為了演示使用,為了讓大家更加理解AOP的作用,這里以實際場景作為例子。
第一個例子,我們知道MyBatis的事務默認是不會自動提交的,因此在編程的時候我們必須在增刪改完畢之后調用SqlSession的commit()方法進行事務提交,這非常麻煩,下面利用AOP簡單寫一段代碼幫助我們自動提交事務(這段代碼我個人測試過可用):
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public class TransactionHandler { public void commit(JoinPoint jp) { Object obj = jp.getTarget(); if (obj instanceof MailDao) { Signature signature = jp.getSignature(); if (signature instanceof MethodSignature) { SqlSession sqlSession = SqlSessionThrealLocalUtil.getSqlSession(); MethodSignature methodSignature = (MethodSignature)signature; Method method = methodSignature.getMethod(); String methodName = method.getName(); if (methodName.startsWith("insert") || methodName.startsWith("update") || methodName.startsWith("delete")) { sqlSession.commit(); } sqlSession.close(); } } } }
這種場景下我們要使用的aop標簽為 aop:after,即切在方法調用之后。
這里我做了一個SqlSessionThreadLocalUtil,每次打開會話的時候,都通過SqlSessionThreadLocalUtil把當前會話SqlSession放到ThreadLocal中,看到通過TransactionHandler,可以實現兩個功能:
insert、update、delete操作事務自動提交
對SqlSession進行close(),這樣就不需要在業務代碼里面關閉會話了,因為有些時候我們寫業務代碼的時候會忘記關閉SqlSession,這樣可能會造成內存句柄的膨脹,因此這部分切面也一并做了
整個過程,業務代碼是不知道的,而TransactionHandler的內容可以充分再多處場景下進行復用。
第二個例子是權限控制的例子,不管是從安全角度考慮還是從業務角度考慮,我們在開發一個Web系統的時候不可能所有請求都對所有用戶開放,因此這里就需要做一層權限控制了,大家看AOP作用的時候想必也肯定會看到AOP可以做權限控制,這里我就演示一下如何使用AOP做權限控制。我們知道原生的Spring MVC,Java類是實現Controller接口的,基于此,利用AOP做權限控制的大致代碼如下(這段代碼純粹就是一段示例,我構建的Maven工程是一個普通的Java工程,因此沒有驗證過):
/** * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7003082.html */ public class PermissionHandler { public void hasPermission(JoinPoint jp) throws Exception { Object obj = jp.getTarget(); if (obj instanceof Controller) { Signature signature = jp.getSignature(); MethodSignature methodSignature = (MethodSignature)signature; // 獲取方法簽名 Method method = methodSignature.getMethod(); // 獲取方法參數 Object[] args = jp.getArgs(); // Controller中唯一一個方法的方法簽名ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; // 這里對這個方法做一層判斷 if ("handleRequest".equals(method.getName()) && args.length == 2) { Object firstArg = args[0]; if (obj instanceof HttpServletRequest) { HttpServletRequest request = (HttpServletRequest)firstArg; // 獲取用戶id long userId = Long.parseLong(request.getParameter("userId")); // 獲取當前請求路徑 String requestUri = request.getRequestURI(); if(!PermissionUtil.hasPermission(userId, requestUri)) { throw new Exception("沒有權限"); } } } } } }
毫無疑問這種場景下我們要使用的aop標簽為 aop:before。這里我寫得很簡單,獲取當前用戶id與請求路徑,根據這兩者,判斷該用戶是否有權限訪問該請求,大家明白意思即可。
到此,關于“Spring AOP標簽怎么使用”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。