1. Spring 基本概念
AOP(Aspect Oriented Programming)稱為面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,比如日志,事務,權限等待,Struts2的攔截器設計就是基於AOP的思想,是個比較經典的例子。
在不改變原有的邏輯的基礎上,增加一些額外的功能。代理也是這個功能,讀寫分離也能用aop來做。
2. 原理圖:
我們希望業務開發人員只關心中間部分,不再需要關系開啟和關閉數據庫連接的情況,同時也避免了代碼重復和可能出現的問題。
3. 代碼
public interface UserService { //刪 void save(); String getStr(); void setStr(String str); }
public class UserServiceImpl implements UserService { private String str = "0"; public String getStr() { return str; } public void setStr(String str) { this.str = str; } @Override public void save() { System.out.println("--------- save --------------"); } }
import org.aspectj.lang.ProceedingJoinPoint; /** * 自定義通知類 */ public class MyAdvice { //before 前置通知 在目標方法前調用 public void before() { System.out.println("before"); } //after 最終通知(后置通知)在目標方法后調用,無論是否出現異常都會執行 finally public void after() { System.out.println("after"); } //afterReturning 成功通知(后置通知)在目標方法執行后,並且執行成功,如果方法出現異常則不調用 public void afterReturning() { System.out.println("afterReturning"); } //afterThrowing 異常通知(后置通知)在目標方法執行出現異常的時候才會調用 public void afterThrowing() { System.out.println("afterThrowing"); } //around 環繞通知 需要我們手動調用目標方法,並且可以設置通知 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before"); Object proceed = pjp.proceed(); System.out.println("around after"); return proceed; } }
import com.bing.aop.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext_aop.xml") public class AopTest { @Resource(name="userService") UserService us; @Test public void Test2() { us.save(); } }
配置文件:文件名(applicationContext_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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 目標對象 --> <bean name="userService" class="com.bing.aop.service.UserServiceImpl"></bean> <!-- 通知對象 --> <bean name="myAdvice" class="com.bing.aop.MyAdvice"></bean> <aop:config> <!-- 切入點 expression 切入點表達式 可以配置要增強的方法 public void com.bing.aop.service.UserServiceImpl.save() * com.bing.aop.service.*ServiceImpl.*(..) id 就是唯一標識 --> <aop:pointcut expression="execution(* com.bing.aop.service.*ServiceImpl.*(..))" id="servicePc"/> <!-- 切面 通知+切入點 --> <aop:aspect ref="myAdvice"> <!-- 通知類型 --> <aop:before method="before" pointcut-ref="servicePc"/> <!-- 最終通知 后置通知 --> <aop:after method="after" pointcut-ref="servicePc"/> <!-- 成功通知 后置通知 --> <aop:after-returning method="afterReturning" pointcut-ref="servicePc"/> <!-- 異常通知 后置通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="servicePc"/> <!-- 環繞通知--> <aop:around method="around" pointcut-ref="servicePc"/> </aop:aspect> </aop:config> </beans>
運行結果:
到這里其實發現這個AOP沒有什么實際用處,前后中間執行的東西都沒有關系,並不能體現真正的AOP思想。
我們將上述代碼修改一下:修改環繞通知的方法。
import com.bing.aop.service.UserService; import com.bing.aop.service.UserServiceImpl; import org.aspectj.lang.ProceedingJoinPoint; /** * 自定義通知類 */ public class MyAdvice { //before 前置通知 在目標方法前調用 public void before() { System.out.println("before"); } //after 最終通知(后置通知)在目標方法后調用,無論是否出現異常都會執行 finally public void after() { System.out.println("after"); } //afterReturning 成功通知(后置通知)在目標方法執行后,並且執行成功,如果方法出現異常則不調用 public void afterReturning() { System.out.println("afterReturning"); } //afterThrowing 異常通知(后置通知)在目標方法執行出現異常的時候才會調用 public void afterThrowing() { System.out.println("afterThrowing"); } //around 環繞通知 需要我們手動調用目標方法,並且可以設置通知 public Object around(ProceedingJoinPoint pjp) throws Throwable { UserService userService = (UserServiceImpl)pjp.getTarget(); Object proceed = null; if("1".equals(userService.getStr()) || "setStr".equals(pjp.getSignature().getName())){ proceed = pjp.proceed(); } return proceed; } }
import com.bing.aop.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext_aop.xml") public class AopTest { @Resource(name="userService") UserService us; @Test public void Test2() { System.out.println("設置前:------------Str == 0"); us.save(); us.setStr("1"); System.out.println("設置前:------------Str == 1"); us.save(); } }
然后將配置文件的其他通知都去掉:
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 目標對象 --> <bean name="userService" class="com.bing.aop.service.UserServiceImpl"></bean> <!-- 通知對象 --> <bean name="myAdvice" class="com.bing.aop.MyAdvice"></bean> <aop:config> <!-- 切入點 expression 切入點表達式 可以配置要增強的方法 public void com.bing.aop.service.UserServiceImpl.save() * com.bing.aop.service.*ServiceImpl.*(..) id 就是唯一標識 --> <aop:pointcut expression="execution(* com.bing.aop.service.*ServiceImpl.*(..))" id="servicePc"/> <!-- 切面 通知+切入點 --> <aop:aspect ref="myAdvice"> <!-- 環繞通知--> <aop:around method="around" pointcut-ref="servicePc"/> </aop:aspect> </aop:config> </beans>
運行效果:
把實體類和MyAdvice 結合起來,可以更加方便的寫你想要處理的邏輯。
XML 用的越來越少了,現在大部分都使用注解了,下面是使用注解的Demo
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @ComponentScan("com.hundsun.cop.aop") @Configuration @EnableAspectJAutoProxy public class AppConfig { }
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class MyAdvice { //設置切點 @Pointcut("execution(public * com.hundsun.cop.aop.UserService.*(..))") public void execution(){ } // 執行前 @Before("MyAdvice.execution()") public void before(){ } // 執行后 @After("MyAdvice.execution()") public void after() { System.out.println("after"); } //afterReturning 成功通知(后置通知)在目標方法執行后,並且執行成功,如果方法出現異常則不調用 @AfterReturning("MyAdvice.execution()") public void afterReturning() { System.out.println("afterReturning"); } //afterThrowing 異常通知(后置通知)在目標方法執行出現異常的時候才會調用 @AfterThrowing("MyAdvice.execution()") public void afterThrowing() { System.out.println("afterThrowing"); } //around 環繞通知 需要我們手動調用目標方法,並且可以設置通知 @Around("MyAdvice.execution()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before"); Object proceed = pjp.proceed(); System.out.println("around after"); return proceed; } }
public interface UserService { void save(); String getStr(); void setStr(String str); }
@Component public class UserServiceImpl implements UserService { private String str = "0"; public String getStr() { return str; } public void setStr(String str) { this.str = str; } @Override public void save() { System.out.println("--------- save --------------"); } }
import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class); UserService userService = annotationConfigApplicationContext.getBean(UserService.class); userService.save(); } }
運行結果
通過注解來表示切點的方式:
import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Slf4j @Aspect @Component public class ExecTimeAdvice { //設置切點(設置成注解類,后面需要的地方加上注解即可) @Pointcut("@annotation(com.hundsun.o45.repeat.advice.ExecTimeMonitor)") public void execution(){ } @Around("ExecTimeAdvice.execution()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("AOP測試"); return null; } }
注解:
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(value = {ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ExecTimeMonitor { }
不喜歡誇誇奇談,實戰才是真理。代碼是說明原理的最可靠的方式。