AOP(Aspect Oriented Programming),即面向切面編程。
1、OOP回顧
在介紹AOP之前先來回顧一下大家都比較熟悉的OOP(Object Oriented Programming)。OOP主要是為了實現編程的重用性、靈活性和擴展性。它的幾個特征分別是繼承、封裝、多態和抽象。OOP重點體現在編程架構,強調的是類之間的層次關系。
2、OOP缺陷
為了更好的說明OOP的概念,我們接下來講一個OOP的實例,重點分析OOP存在哪些缺陷,以便更好的理解AOP的相關內容。
先看如下的一張圖:
上面這張圖有三個類:Dog,Cat和Duck,他們都有一個方法run。按照OOP的設計理念,我們很容易就會想到抽象出一個Animal父類,同時讓這三個子類繼承Animal父類。這樣的設計可以用如下的圖示表示:
在OOP思想中,我們會使用大量的類似上圖的編程方式,對類進行抽象、繼承、封裝和多態來實現編程的重用性、靈活性和擴展性。但是這樣的編程仍然有一定的局限性,有時候,OOP並不能很好解決我們再實際開發中遇到的問題。為了說明這個問題,看下面的圖示:
看到上面的圖,我們暫時還不能發現有什么問題。為了大家便於理解,接下來我來給大家講解一下上面類圖的實現過程。描述如下:馬戲團有一條表演的小狗,這條小狗可以跑和跳,但是它完成跑和跳兩個動作之前必須是在接到馴獸師發出的命令后,同時完成跑和跳的動作之后,馴獸師會給與響應的獎勵,比如一塊肉。
了解了實現過程之后,我們在來看一下具體的代碼。
public class Dog { public void run() { System.out.println("馴獸師發出命令!") System.out.println("小狗開始跑!"); System.out.pringln("馴獸師給與獎勵"); } public void jump() { System.out.println("馴獸師發出命令!") System.out.println("小狗開始跳!"); System.out.pringln("馴獸師給與獎勵"); } }
仔細看上面的代碼,我們可以看出在run方法和jump方法中,存在一些相同的內容(馴獸師發出命令和給與獎勵),這些內容並不能完全進行抽象,即不能按照OOP編程思想進行處理。類似這樣的情況同樣會出現在我們編程中的很多地方,例如:日志記錄、性能統計、安全控制、事務處理、異常處理等等。但是這樣的情況該如何解決呢?這就引入了AOP編程思想。
3、AOP簡介
AOP為Aspect Oriented Programming的縮寫,即面向切面編程(也叫面向方面),是一種可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。
4、AOP實現實例
為了大家更好的理解AOP如何實現,接下來我們優化一下上述代碼。
首先是Dog類
public interface Animal { public void run(); public void jump(); } public class Dog implements Animal{ public void run(){ System.out.println("小狗開始跑!"); } public void jump(){ System.out.println("小狗開始跳!"); } }
對比之前的代碼我們可以明顯看出,我們將關於馴獸師的相關內容從run和jump中進行了抽取,接下來,我們如何在程序運行中將關於馴獸師的動作加入到程序中呢?這就是我們這次用到的AOP實現的核心技術動態代理(Dynamic Proxy)。具體代碼如下:
public class MyProxy implements InvocationHandler{ private Object targetObject; public Object createProxyInstance(Object targetObject) { this.targetObject = targetObject; return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { command(); Object ret = method.invoke(targetObject, args); award(); return ret; } private void command() { System.out.println("馴獸師發出命令!"); } private void award(){ System.out.println("馴獸師給與獎勵!"); } }
上述代碼實現完成之后,我們改如何調用呢?參考代碼如下:
public class Client { public static void main(String[] args) { MyProxy hander = new MyProxy(); Animal dog = (Animal)hander.createProxyInstance(new Dog()); dog.run(); dog.jump(); } }
執行結果如下:
關於AOP編程的實例演示就完成了,接下來重新回顧一下AOP與OOP的相關概念。
5、AOP與OOP的關系
OOP針對業務處理過程的實體(Dog、Cat、Duck)及其屬性和行為(run)進行抽象封裝,以獲得更加清晰高效的邏輯單元划分。而AOP則是針對業務處理過程中(run或jump)的切面(command和award)進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。這兩種設計思想在目標上有着本質的差異。
一、AOP案例如下:
1、創建如下項目結構
2、在com.entity包下創建User.java

1 package com.entity; 2 3 public class User { 4 private Integer id; // 用戶ID 5 private String username; // 用戶名 6 private String password; // 密碼 7 private String email; // 電子郵件 8 9 // getter & setter 10 public Integer getId() { 11 return id; 12 } 13 14 public void setId(Integer id) { 15 this.id = id; 16 } 17 18 public String getUsername() { 19 return username; 20 } 21 22 public void setUsername(String username) { 23 this.username = username; 24 } 25 26 public String getPassword() { 27 return password; 28 } 29 30 public void setPassword(String password) { 31 this.password = password; 32 } 33 34 public String getEmail() { 35 return email; 36 } 37 38 public void setEmail(String email) { 39 this.email = email; 40 } 41 42 @Override 43 public String toString() { 44 return "User [email=" + email + ", id=" + id + ", password=" + password 45 + ", username=" + username + "]"; 46 } 47 48 49 50 }
3、在com.dao包下創建IUserDao.java

1 package com.dao; 2 3 import com.entity.User; 4 5 public interface IUserDao { 6 public void save(User user); 7 }
4、在com.dao.impl包下創建UserDaoImpl.java

1 package com.dao.impl; 2 3 import com.dao.IUserDao; 4 import com.entity.User; 5 /** 6 * 用戶DAO類,實現IDao接口,負責User類的持久化操作 7 */ 8 public class UserDaoImpl implements IUserDao{ 9 /** 10 * 保存 11 */ 12 public void save(User user) { 13 // 這里並未實現完整的數據庫操作,僅為說明問題 14 System.out.println("保存用戶信息到數據庫"); 15 } 16 }
5、在com.biz包下創建IUserBiz.java

1 package com.biz; 2 3 import com.entity.User; 4 5 public interface IUserBiz { 6 public void addNewUser(User user); 7 }
6、在com.biz.impl包下創建UserBizImpl.java

1 package com.biz.impl; 2 3 import com.biz.IUserBiz; 4 import com.dao.impl.UserDaoImpl; 5 import com.entity.User; 6 7 8 /** 9 * 用戶業務類,實現對User功能的業務管理 10 */ 11 public class UserBizImpl implements IUserBiz { 12 13 // 聲明接口類型的引用,和具體實現類解耦合 14 private UserDaoImpl dao; 15 16 17 public void addNewUser(User user) { 18 //調用用戶dao的方法保存用戶信息 19 dao.save(user); 20 21 } 22 23 public UserDaoImpl getDao() { 24 return dao; 25 } 26 27 public void setDao(UserDaoImpl dao) { 28 this.dao = dao; 29 } 30 31 32 public UserBizImpl() { 33 } 34 35 public UserBizImpl(UserDaoImpl dao) { 36 this.dao = dao; 37 } 38 39 40 41 42 }
7、在com.aop包下創建LoggerBefore.java

1 package com.aop; 2 3 import java.lang.reflect.Method; 4 import java.util.Arrays; 5 6 import org.apache.log4j.Logger; 7 import org.springframework.aop.MethodBeforeAdvice; 8 9 /** 10 * 通過MethodBeforeAdvice實現前置增強 11 */ 12 public class LoggerBefore implements MethodBeforeAdvice { 13 private static final Logger log = Logger.getLogger(LoggerBefore.class); 14 15 public void before(Method method, Object[] arguments, Object target) 16 throws Throwable { 17 // Arrays.toString()數組內容轉換為字符串 18 log.info("調用 " + target + "的" + method.getName() + "方法。方法傳入參數:" 19 + Arrays.toString(arguments)); 20 } 21 22 }
8、在com.aop包下創建LoggerAfterReturning.java

1 package com.aop; 2 3 import java.lang.reflect.Method; 4 import org.apache.log4j.Logger; 5 import org.springframework.aop.AfterReturningAdvice; 6 /** 7 * 通過AfterReturningAdvice實現后置增強 8 */ 9 public class LoggerAfterReturning implements AfterReturningAdvice { 10 private static final Logger log = Logger.getLogger(LoggerAfterReturning.class); 11 12 public void afterReturning(Object returnValue, Method method, 13 Object[] arguments, Object target) throws Throwable { 14 log.info("調用 " + target + "的" + method.getName() + " 方法方法返回值為" 15 + returnValue); 16 } 17 18 }
9、在src下創建applicationContext.xml

1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 7 http://www.springframework.org/schema/aop 8 http://www.springframework.org/schema/aop/spring-aop-3.1.xsd"> 9 <!-- 實例化接口對象 --> 10 <bean id="userDao" class="com.dao.impl.UserDaoImpl"></bean> 11 12 <!-- 實例化業務對象 --> 13 <bean id="biz" class="com.biz.impl.UserBizImpl"> 14 <!-- 注入方式1:設置注入接口對象 --> 15 <!-- 16 <property name="dao" ref="userDao"/> 17 --> 18 <!-- 注入方式2:構造注入對象 --> 19 <!-- type表示參數的類型,index表示參數的位置索引 --> 20 <constructor-arg index="0" ref="userDao" /> 21 </bean> 22 23 <!-- 由於默認走jdk代理,有時可能找不到代理,所以此處指定走cglib代理 --> 24 <aop:aspectj-autoproxy proxy-target-class="true"/> 25 26 27 28 <!-- 實例化日志前置增強對象 --> 29 <bean id="loggerBefore" class="com.aop.LoggerBefore"></bean> 30 31 <!-- 實例化日志后置增強對象 --> 32 <bean id="loggerAfterReturning" class="com.aop.LoggerAfterReturning"></bean> 33 34 <!-- 35 面向切面編程的配置即將某個功能動態的配置到某個流程中,而不改變后台代碼 36 實現業務代碼和日志代碼是完全分離的,經過配置后,不做任何代碼的修改,就在addNewUser方法前后實現了日志輸出 37 --> 38 <aop:config> 39 <!-- 切入點的配置 --> 40 <aop:pointcut id="pointcut" 41 expression="execution(public void addNewUser(com.entity.User))" /> 42 <!-- 將增強處理和切入點結合, --> 43 <aop:advisor pointcut-ref="pointcut" advice-ref="loggerBefore" /> 44 <aop:advisor pointcut-ref="pointcut" advice-ref="loggerAfterReturning" /> 45 </aop:config> 46 </beans> 47 48 applicationContext.xml
10、在src下創建log4j.properties

1 # rootLogger是所有日志的根日志,修改該日志屬性將對所有日志起作用 2 # 下面的屬性配置中,所有日志的輸出級別是info,輸出源是console 3 log4j.rootLogger=info,console 4 # 定義輸出源的輸入位置是控制台 5 log4j.appender.console=org.apache.log4j.ConsoleAppender 6 # 定義輸出日志的布局采用的類 7 log4j.appender.console.layout=org.apache.log4j.PatternLayout 8 # 定義日志輸出布局 9 log4j.appender.console.layout.ConversionPattern=%d %p [%c]%n - %m%n
11、在com.test包下創建Test.java

1 package com.test; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 import com.biz.IUserBiz; 7 import com.entity.User; 8 9 public class Test { 10 11 public static void main(String[] args) { 12 //讀取和加載xml配置文件 13 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 14 //獲取配置文件的bean的實例 15 IUserBiz biz = (IUserBiz) ctx.getBean("biz"); 16 17 //實例化User對象 18 User user = new User(); 19 user.setId(1); 20 user.setUsername("test"); 21 user.setPassword("123456"); 22 user.setEmail("test@pbdevj.com"); 23 24 //添加信息 25 biz.addNewUser(user); 26 } 27 28 }
12、運行結果如下: