Aspect oritention programming(面向切面編程),AOP是一種思想,高度歸納的話是「橫向重複,縱向抽取」,如何理解呢?舉個例子:訪問頁面時須要權限認證,若是每一個頁面都去實現方法顯然是不合適的,這個時候咱們就能夠利用切面編程。java
每一個頁面都去實現這個方法就是橫向的重複,咱們直接從中切入,封裝一個與主業務無關的權限驗證的公共方法,這樣能夠減小系統的重複代碼,下降模塊之間的耦合度,簡單的示意圖以下:spring
AOP用來封裝橫切關注點,具體能夠在下面的場景中使用:express
Authentication 權限編程
Caching 緩存緩存
Context passing 內容傳遞性能優化
Error handling 錯誤處理app
Lazy loading 懶加載框架
Debugging 調試 lide
ogging, tracing, profiling and monitoring 記錄跟蹤 優化 校準函數
Performance optimization 性能優化
Persistence 持久化
Resource pooling 資源池
Synchronization 同步
Transactions 事務
1.鏈接點(Joinpoint) 所謂鏈接點是指那些可能被攔截到的方法。例如:全部能夠增長的方法
2.切點(Pointcut) 已經被加強的鏈接點
3.加強(Advice) 加強的代碼
4.目標對象(Target) 目標類,須要被代理的類
5.織入(Weaving) 是指把加強advice應用到目標對象target來建立新的代理對象proxy的過程
6.代理(Proxy) 一個類被AOP織入加強後,就產生出了一個結果類,它是融合了原類和加強邏輯的代理類。
7.切面(Aspect)切入點+通知
通知類型:Spring按照通知Advice在目標類方法的鏈接點位置,能夠分爲5類
前置通知 (在目標方法執行前實施加強)
後置通知(在目標方法執行後實施加強)
環繞通知(在目標方法執行先後實施增長)
異常拋出通知(在方法跑出異常時通知)
引介通知(在目標類中添加一些新的方法和屬性)
AOP的實現關鍵在於AOP框架自動建立的AOP代理。AOP代理主要分爲兩大類:
靜態代理:使用AOP框架提供的命令進行編譯,從而在編譯階段就能夠生成AOP代理類,所以也稱爲編譯時加強;靜態代理一Aspectj爲表明。
動態代理:在運行時藉助於JDK動態代理,CGLIB等在內存中臨時生成AOP動態代理類,所以也被稱爲運行時加強,Spring AOP用的就是動態代理。
例子:在增長員工和刪除員工時增長事務處理
//員工類 public class Employee { private Integer uid; public void setUid(Integer uid) { this.uid = uid; } public Integer getUid() { return uid; } private Integer age; private String name; public Integer getAge() { return age; } public String getName() { return name; } public void setAge(Integer age) { this.age = age; } public void setName(String name) { this.name = name; } }
員工接口:
//員工接口 public interface EmployeeService { //新增方法 void addEmployee(Employee employee); //刪除方法 void deleteEmployee(Integer uid); }
員工實現:
//員工方法實現 public class EmployeeServiceImpl implements EmployeeService { @Override public void addEmployee(Employee employee) { System.out.println("新增員工"); } @Override public void deleteEmployee(Integer uid) { System.out.println("刪除員工"); } }
事務類:
//事務類 public class MyTransaction { //開啓事務 public void before(){ System.out.println("開啓事務"); } //提交事務 public void after(){ System.out.println("提交事務"); } }
代理類:
//代理類 public class ProxyEmployee implements EmployeeService { // private EmployeeService employeeService; private MyTransaction myTransaction; public ProxyEmployee(EmployeeService employeeService,MyTransaction myTransaction) { this.employeeService=employeeService; this.myTransaction=myTransaction; } @Override public void addEmployee(Employee employee) { myTransaction.before(); employeeService.addEmployee(employee); myTransaction.after(); } @Override public void deleteEmployee(Integer uid) { myTransaction.before(); employeeService.deleteEmployee(uid); myTransaction.after(); } }
測試:
@Test public void fun1(){ MyTransaction transaction = new MyTransaction(); EmployeeService EmployeeService = new EmployeeServiceImpl(); //產生靜態代理對象 ProxyEmployee proxy = new ProxyEmployee(EmployeeService, transaction); proxy.addEmployee(null); proxy.deleteEmployee(0); }
結果:
這是靜態代理的實現方式,靜態代理有明顯的缺點:
一、代理對象的一個接口只服務於一種類型的對象,若是要代理的方法不少,勢必要爲每一種方法都進行代理,靜態代理在程序規模稍大時就沒法勝任了。
二、若是接口增長一個方法,好比 EmployeeService增長修改 updateEmployee()方法,則除了全部實現類須要實現這個方法外,全部代理類也須要實現此方法。增長了代碼維護的複雜度。
動態代理就不要本身手動生成代理類了,咱們去掉 ProxyEmployee.java 類,增長一個 ObjectInterceptor.java 類
public class ObjectInterceptor implements InvocationHandler { //目標類 private Object target; //切面類(這裏指事務類) private MyTransaction transaction; //經過構造器賦值 public ObjectInterceptor(Object target,MyTransaction transaction){ this.target = target; this.transaction = transaction; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { this.transaction.before(); method.invoke(target,args); this.transaction.after(); return null; } }
測試:
@Test public void fun2(){ //目標類 Object target = new EmployeeServiceImpl (); //事務類 MyTransaction transaction = new MyTransaction(); ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction); /** * 三個參數的含義: * 一、目標類的類加載器 * 二、目標類全部實現的接口 * 三、攔截器 */ EmployeeService employeeService = (EmployeeService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), proxyObject); employeeService.addEmployee(null); employeeService.deleteEmployee(0); }
結果:
spring 有兩種方式實現AOP的:一種是採用聲明的方式來實現(基於XML),一種是採用註解的方式來實現(基於AspectJ)
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--目標類--> <bean name="employeeService" class="com.yuanqinnan.aop.EmployeeServiceImpl"></bean> <bean name="transaction" class="com.yuanqinnan.aop.MyTransaction"></bean> <aop:config> <aop:aspect ref="transaction"> <aop:pointcut id="pointcut" expression="execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))"/> <!-- 配置前置通知,注意 method 的值要和 對應切面的類方法名稱相同 --> <aop:before method="before" pointcut-ref="pointcut"></aop:before> <!--配置後置通知,注意 method 的值要和 對應切面的類方法名稱相同--> <aop:after-returning method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
測試:
@Test public void fun3(){ ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml"); EmployeeService employeeService = (EmployeeService) context.getBean("employeeService"); employeeService.addEmployee(null); }
結果:
補充:
1.aop:pointcut若是位於aop:aspect元素中,則命名切點只能被當前aop:aspect內定義的元素訪問到,爲了能被整個aop:config元素中定義的全部加強訪問,則必須在aop:config下定義切點。
2.若是在aop:config元素下直接定義aop:pointcut,必須保證aop:pointcut在aop:aspect以前定義。aop:config下還能夠定義aop:advisor,三者在aop:config中的配置有前後順序的要求:首先必須是aop:pointcut,而後是aop:advisor,最後是aop:aspect。而在aop:aspect中定義的aop:pointcut則沒有前後順序的要求,能夠在任何位置定義。
aop:pointcut:用來定義切入點,該切入點能夠重用;
aop:advisor:用來定義只有一個通知和一個切入點的切面;
aop:aspect:用來定義切面,該切面能夠包含多個切入點和通知,並且標籤內部的通知和切入點定義是無序的;和advisor的區別就在此,advisor只包含一個通知和一個切入點。
3.在使用spring框架配置AOP的時候,不論是經過XML配置文件仍是註解的方式都須要定義pointcut"切入點" 例如定義切入點表達式 execution(* com.sample.service.impl...(..)) execution()是最經常使用的切點函數,其語法以下所示:
整個表達式能夠分爲五個部分: (1)、execution(): 表達式主體。
(2)、第一個號:表示返回類型,號表示全部的類型。
(3)、包名:表示須要攔截的包名,後面的兩個句點表示當前包和當前包的全部子包,com.sample.service.impl包、子孫包下全部類的方法。
(4)、第二個號:表示類名,號表示全部的類。
(5)、(..):最後這個星號表示方法名,號表示全部的方法,後面括弧裏面表示方法的參數,兩個句點表示任何參數。
新建註解類:
@Component @Aspect public class AopAspectJ { /** * 必須爲final String類型的,註解裏要使用的變量只能是靜態常量類型的 */ public static final String EDP="execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))"; /** * 切面的前置方法 即方法執行前攔截到的方法 * 在目標方法執行以前的通知 * @param jp */ @Before(EDP) public void doBefore(JoinPoint jp){ System.out.println("=========執行前置通知=========="); } /** * 在方法正常執行經過以後執行的通知叫作返回通知 * 能夠返回到方法的返回值 在註解後加入returning * @param jp * @param result */ @AfterReturning(value=EDP,returning="result") public void doAfterReturning(JoinPoint jp,String result){ System.out.println("===========執行後置通知============"); } /** * 最終通知:目標方法調用以後執行的通知(不管目標方法是否出現異常均執行) * @param jp */ @After(value=EDP) public void doAfter(JoinPoint jp){ System.out.println("===========執行最終通知============"); } /** * 環繞通知:目標方法調用先後執行的通知,能夠在方法調用先後完成自定義的行爲。 * @param pjp * @return * @throws Throwable */ @Around(EDP) public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======執行環繞通知開始========="); // 調用方法的參數 Object[] args = pjp.getArgs(); // 調用的方法名 String method = pjp.getSignature().getName(); // 獲取目標對象 Object target = pjp.getTarget(); // 執行完方法的返回值 // 調用proceed()方法,就會觸發切入點方法執行 Object result=pjp.proceed(); System.out.println("輸出,方法名:" + method + ";目標對象:" + target + ";返回值:" + result); System.out.println("======執行環繞通知結束========="); return result; } /** * 在目標方法非正常執行完成, 拋出異常的時候會走此方法 * @param jp * @param ex */ @AfterThrowing(value=EDP,throwing="ex") public void doAfterThrowing(JoinPoint jp,Exception ex) { System.out.println("===========執行異常通知============"); } }
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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.yuanqinnan.aop" ></context:component-scan> <!-- 聲明spring對@AspectJ的支持 --> <aop:aspectj-autoproxy/> <!--目標類--> <bean name="employeeService" class="com.yuanqinnan.aop.EmployeeServiceImpl"></bean> </beans>
測試:
@Test public void fun4(){ ApplicationContext act = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml"); EmployeeService employeeService = (EmployeeService) act.getBean("employeeService"); employeeService.addEmployee(null); }
結果:
pringAOP的知識就總結到這裏