ssm-spring之aop(xml+annotation)
AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期間動態代理實作程式功能的統一維護的一種技術, AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函式式編程的一種衍生范型,利用AOP可以對業務邏輯的各個部分進行隔離, 從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率,
主要功能應用場景:日志記錄,性能統計,安全控制,事務處理,例外處理等等,將這些場景的代碼從業務邏輯代碼中劃分出來, 通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候不影響業務邏輯的代碼, 通俗的理解:依賴注入維度是物件,而aop注入維度是方法,在Spring的應用中,aop主要分為配置和注解兩種實作方式,下面舉例簡單分析:
pom依賴
不管是配置還是注解,都需要在pom檔案中添加以下依賴:<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
配置實作AOP
現在假設有這樣的場景:服務登錄->進行各種業務場景->服務退出登錄,下面模擬這個場景的簡單實作:基礎實作:
- 撰寫service:
- 撰寫service實作類:
- 添加bean配置:
- 撰寫測驗代碼:
- 查看運行結果:
public interface UserService {
void login(String token);
void logout();
}
public class UserServiceImpl implements UserService {
@Override
public void login(String token) {
System.out.println("執行登錄:登錄成功 \ttoken:" + token);
}
@Override
public void logout() {
System.out.println("退出登錄");
}
}
<bean id="userService" />
public class AopTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.login("123");
System.out.println("模擬各種業務場景...");
userService.logout();
}
}
簡單的場景實作已經完成,現在假設專案經理說,需要添加登錄登出的日志搜集看看每天多少用戶登錄過,現在一些同學可能會想到在登錄登出介面中添加日志代碼, 這樣確實可以實作專案經理的需求,但是如果后期專案經理需求增加,需要在其他復雜業務介面上也添加日志,那我們可能要進行多次編碼并且最大的問題是會改動原有的實作代碼, 稍不注意一個簡單的日志邏輯就可能對原有邏輯產生新的bug,那更好的方法是使用AOP動態的往介面實作中注入日志代碼,下面看看如何在不改動上述代碼的基礎上, 實作日志的織入,
aop注入到指定業務方法:
- 撰寫日志實作方法:
- aop相關的xml配置,注解上說明了各屬性的含義:
- 再次運行上面的測驗代碼:
public class Log {
public void logBefore() {
System.out.println("注解日志:======業務方法執行前注入======(前)" );
}
public void logAfter() {
System.out.println("注解日志:======業務方法執行后注入======(后)" );
}
}
<bean id="log" />
<aop:config>
<aop:aspect ref="log">
<aop:pointcut id="p1" expression="execution(void com.zx.demo.spring.aop.UserService.login(String))"/>
<aop:before method="logBefore" pointcut-ref="p1"/>
<aop:after method="logAfter" pointcut-ref="p1"/>
</aop:aspect>
</aop:config>

兩次運行結果對比可以發現,第二次在未改動業務代碼實作邏輯的前提下成功完成了aop日志注入,
aop注入到多個業務方法:
- aop相關的xml配置,注解上說明了各屬性的含義:
- 第三次運行上面的測驗代碼:

和上一次運行對比可以發現,這次不僅在登錄邏輯前后注入了日志,退出登錄也有注入,
aop注入之環繞攔截
前面個的兩個注入方法可以在多個業務方法邏輯的前后實作注入日志,before和after通知的局限是無法獲取業務引數也無法攔截阻斷業務方法邏輯,
如果現在有這樣的需求在不改動原有邏輯代碼的前提下,將長度小于5的token視為無效token不執行登錄邏輯,這里要用到一個新的環繞通知:around,
下面來看個例子:
- 撰寫需要注入的驗證實作:
- 此物件只能在around通知場景;
- 注入方法中若沒有添加此引數物件,將會阻斷攔截后續通知和業務方法邏輯;
- 注入方法中若有添加此引數物件,但方法中未呼叫該物件的proceed()方法,同樣會阻斷攔截后續通知和業務方法邏輯;
- 從此物件中可以獲取到業務方法引數值;
- 要阻斷后續通知,必須在aop配置中將around通知配置在其他通知之前以順序執行,否則無效,
- 撰寫aop配置:
- token長度>=5的場景測驗:
- token長度<5的場景測驗:
public class Verify {
public void check(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
if (args == null || !(args[0] instanceof String) || args[0].toString().length() < 5) {
System.out.println("登錄例外:無效token");
} else {
//1、在環繞通知場景,當且僅當呼叫此方法,讓后續通知和業務邏輯才會繼續執行,否則將阻斷和攔截后續通知和業務邏輯,
//2、若本方法入口沒有添加引數ProceedingJoinPoint,默認阻斷攔截后續通知和業務邏輯,
joinPoint.proceed();
}
}
}
關于ProceedingJoinPoint物件需要注意:
public class AopTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.login("12345");
}
}
此場景會走到check方法的else邏輯中,在執行完joinPoint.proceed()后,業務方法可正常登錄:
public class AopTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.login("123");
}
}
此場景沒有執行joinPoint.proceed(),因此登錄業務邏輯將會被攔截阻斷:
- 如果配置了多個切面,則按照順序依次執行;
- 環繞通知只會阻斷配置在其后方的通知,其前方的通知會正常注入,
純注解實作AOP
上面已經在不改動業務邏輯代碼的前提下,通過配置的方式實作了代碼注入,除了使用配置實作AOP,還可以采用更簡單的注解方式,- 在spring組態檔中,添加一行配置,使aop支持注解:
- 撰寫切面類:
- 運行測驗代碼:
<aop:aspectj-autoproxy/>
//@Aspect:切面注解
@Aspect
public class AnnotationLog {
//@Before:前置通知,同配置-aop:before
@Before("execution(* com.zx.demo.spring..*(..))")
public void logBefore() {
System.out.println("日志:操作前");
}
//@logAfter:前置通知,同配置-aop:after
@After("execution(* com.zx.demo.spring..*(..))")
public void logAfter() {
System.out.println("日志:操作后");
}
//@Around:前置通知,同配置-aop:around
@Around("execution(* com.zx.demo.spring..*(..))")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("獲取到業務引數:" + joinPoint.getArgs()[0]);
System.out.println("環繞通知開始");
joinPoint.proceed();
System.out.println("環繞通知結束");
}
}

可以看到注解更加簡單的實作了切面注入,并且注解通知的執行順序為:環繞通知->前置通知->業務方法->后置通知,
配置和注解的方式都可以實作切面代碼注入,到了spring boot后,更多的是使用簡介的注解方式,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/302108.html
標籤:Java
