一、AOP簡述
AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護(增強方法)的一種技術。
AOP是OOP(面向對象編程)的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性代碼
經典應用:事務管理、性能監視、緩存、日誌,權限管理等
Spring AOP使用純Java實現,不需要專門的編譯過程和類加載器,在運行期通過代理方式向目標類織入增強代碼
AspectJ是一個基於Java語言的AOP框架,Spring2.0開始,Spring AOP引入對Aspect的支持,AspectJ擴展了Java語言,提供了一個專門的編譯器,在編譯時提供橫向代碼的織入
二、AOP實現原理
aop底層將採用代理機制進行實現。(動態代理模式)
動態代理它可以直接給某一個目標對象生成一個代理對象,而不需要代理類存在。
動態代理與代理模式(靜態代理)原理是一樣的,只是它沒有具體的代理類,直接通過反射生成了一個代理對象。
①:jdk的動態代理方式(spring的aop底層默認使用的是jdk動態代理)
要求:接口 + 實現類
spring底層默認使用該方式創建代理對象
②: cglib方式
要求:實現類(給目標類創建一個子類)
spring 可以使用cglib字節碼增強實現aop。
三、AOP術語
1.target:目標類,需要被代理的類。例如:UserService的實現類UserServiceImpl
2.Joinpoint(連接點):所謂連接點是指那些可能被攔截到的方法。例如:UserServiceImpl所有的方法
3.PointCut 切入點:已經被增強的連接點。例如:addUser()
4.advice 通知/增強,增強代碼。例如:after、before
5. Weaving(織入):是指把增強advice應用到目標對象target來創建新的代理對象proxy的過程.
6.proxy 代理類(類似中介)
7. Aspect(切面): 是切入點pointcut和通知advice的結合
四、實現AOP的方式
1、JDK動態代理
JDK動態代理對“裝飾者”設計模式簡化。使用前提:必須有接口
目標類:接口 + 實現類(被代理的類需要增強的)
通知類:用於存通知 MyAdvice
工廠類:編寫工廠生成代理
主要的兩個方法:
Proxy.newProxyInstance():產生代理類的實例。僅能代理實現至少一個接口的類
ClassLoader:類加載器。固定寫法,和被代理類使用相同的類加載器即可。
Class[] interface:代理類要實現的接口。固定寫法,和被代理類使用相同的接口即可。
InvocationHandler:策略(方案)設計模式的應用。
InvocationHandler中的invoke方法:調用代理類的任何方法,此方法都會執行
Object proxy:代理對象本身的引用。一般用不着。
Method method:當前調用的方法。
Object[] args:當前方法用到的參數
主要的代碼:
public interface ZuFangZi {
public void lookHouse();
public void getMoney(double money);
}
FangDong.java
package com.tf.staticproxy;
public class FangDong implements ZuFangZi {
@Override
public void lookHouse() {
System.out.println("看房東的房子");
}
@Override
public void getMoney(double money) {
System.out.println("房東收到的房租:"+money);
}
}
CreateZhongjie.java
package com.tf.jdkproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//創建代理對象
public class CreateZhongJie {
public static ZuFangZi createZhongJie(){
//創建被代理對象(目標類)
FangDong fangDong = new FangDong();
/*創建代理 對象
* loader:類加載器
* interfaces:被代理對象實現的接口列表
* h:調用代理對象方法的處理器
* */
return (ZuFangZi) Proxy.newProxyInstance(CreateZhongJie.class.getClassLoader(), fangDong.getClass().getInterfaces(), new InvocationHandler() {
/*proxy - 在其上調用方法的代理實例 一般用不到
method - 對應於在代理實例上調用的接口方法的 Method 實例。Method 對象的聲明類將是在其中聲明方法的接口,
該接口可以是代理類賴以繼承方法的代理接口的超接口。
args - 包含傳入代理實例上方法調用的參數值的對象數組,如果接口方法不使用參數,則爲 null。
基本類型的參數被包裝在適當基本包裝器類(如 java.lang.Integer 或 java.lang.Boolean)的實例中。
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("中介開始:"+method.getName());
//擴展代理類
Double zhongjieMoney = 3000.0;
Object obj = null;
if(method.getName().equals("getMoney")){
Double money = (Double)args[0];
System.out.println("中介收取:"+zhongjieMoney+"費");
//通過反射調用目標類的方法
method.invoke(fangDong, new Double[]{money-zhongjieMoney});
}else{
obj = method.invoke(fangDong, args);
}
System.out.println("中介開始:"+method.getName()+"結束");
return obj;
}
});
}
}
Test.java
public class Test {
public static void main(String[] args) {
//獲得創建的中介類
ZuFangZi zhongjie = CreateZhongJie.createZhongJie();
zhongjie.lookHouse();
zhongjie.getMoney(10000);
}
}
2、CGLIB字節碼增強
當沒有接口時,還想生成代理類。
①:沒有接口,只有實現類。
②:採用字節碼增強框架 cglib,在運行時創建目標類的子類,從而對目標類進行增強。
目標類: BokServiceImpl.java
public class BookServiceImpl {
public void add() {
//開啓事務
try{
System.out.println("add");
}catch (Exception e) {
//回滾事務
}
}
public void delete() {
//開啓事務
try {
System.out.println("delete");
//提交事務
}catch (Exception e) {
//回滾事務
}
}
}
通知類:MyAdvice.java
public class MyAdvice {
public void before() {
System.out.println("前置通知---開啓事務");
}
public void after() {
System.out.println("後置通知-----提交事務");
}
public void exceptionAdvice() {
System.out.println("異常通知----回滾事務");
}
}
工廠類 :MyCglibProxyFactory.java
public class MyCglibProxyFactory {
public static BookServiceImpl createProxy(){
//1:創建目標類的對象
BookServiceImpl bookServiceImpl = new BookServiceImpl();
// 2 創建cglib的核心對象
Enhancer enhancer =new Enhancer();
//3 創建增強類的 對象
MyAdvice advice = new MyAdvice();
//4 創建enhancer 父類
enhancer.setSuperclass(bookServiceImpl.getClass());
//5 設置回調函數 通過代理對象調用方法時會執行回調函數中的 MethodInterceptor方法
enhancer.setCallback(new MethodInterceptor(){
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
advice.before();
Object o = null;
try {
//通過反射調用目標類的方法
o = method.invoke(bookServiceImpl, args);
advice.after();
} catch (Exception e) {
advice.exceptionAdvice();
}
return o;//目標類的返回值
}
});
//6 創建這個代理對象
return (BookServiceImpl) enhancer.create();
}
}
測試類:TestCglib.java
//cglib創建對象,不需要有接口 只要有一個類
public class TestCglib {
public static void main(String[] args) {
BookServiceImpl b = MyCglibProxyFactory.createProxy();
b.add();
b.delete();
//被增強的方法爲切入點
}
}
五、SpringAOP聯盟通知類型
- AOP聯盟爲通知Advice定義了org.aopalliance.aop.Advice接口
- Spring按照通知Advice在目標類方法的連接點位置,可以分爲5類
- 前置通知 org.springframework.aop.MethodBeforeAdvice
- 在目標方法執行前實施增強
- 後置通知org.springframework.aop. AfterReturningAdvice
- 在目標方法執行後實施增強
- 環繞通知org.aopalliance.intercept.MethodInterceptor
- 在目標方法執行前後實施增強
- 異常拋出通知org.springframework.aop.ThrowsAdvice
- 在方法拋出異常後實施增強
- 引介通知 org.springframework.aop.IntroductionInterceptor
在目標類中添加一些新的方法和屬性
環繞通知,必須手動執行目標方法放行
try{
//前置通知
//執行目標方法
//後置通知
} catch(){
//拋出異常通知
}
六、spring編寫代理:半自動(瞭解)
- 讓spring 創建代理對象,從spring容器(配置文件)中手動的獲取代理對象
增強類
public class MyAdvice implements MethodInterceptor,MethodBeforeAdvice,AfterReturningAdvice {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
/*環繞通知 調用目標方法前後都會執行
* 是否放行(調用)*/
System.out.println("環繞通知 --- 後置通知");
//調用目標方法
Object o = invocation.proceed();
System.out.println("環繞通知 --- 後置通知");
return o;
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("afterReturning 後置通知");
}
@Override
public void before(Method returnValue, Object[] args, Object target) throws Throwable {
System.out.println("前置通知");
}
}
spring-di.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- spring的半自動 -->
<!-- 目標類 -->
<bean id="bookService" class="com.tf.aop.banzidong.BookServiceImpl"></bean>
<!-- 通知類的對象 -->
<bean id="myAdvice" class="com.tf.aop.banzidong.MyAdvice"></bean>
<!-- 使用spring的ProxyFactoryBean類創建代理對象 -->
<bean id="myProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="com.tf.aop.banzidong.BookService"></property>
<property name="target" value="bookService"></property>
<property name="interceptorNames" value="myAdvice"></property>
</bean>
</beans>
注意: 這三個名字不能變
interfaces: 被代理對象實現的列表
target:目標對象的引用
inteceptorNames:增強類的引用
optimize :強制使用cglib
<property name="optimize" value="true"></property>
底層機制
如果目標類有接口,採用jdk動態代理(默認)
如果沒有接口,採用cglib字節碼增強
如果聲明 optimize = true ,無論是否有接口,都採用cglib
測試類
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:com/tf/aop/banzidong/spring-di.xml")
public class TestBanZiDong {
//spring幹一半,我們自己做一半
//提供接口實現類 、通知類
@Autowired
private BookService bookService;
@Test
public void test() {
System.out.println(bookService);
}
}
七、spring aop編程:全自動【掌握】
- 從spring容器獲得目標類,如果配置aop,spring將自動生成代理。
- 要確定目標類,aspectj 切入點表達式、
- 導包
aspectjweaver-1.8.2.jar
spring-aspects-4.3.5.RELEAxcxcSE.jar
使用<aop:config>進行配置
proxy-target-class="true" 聲明時使用cglib代理
<aop:pointcut>切入點,從目標對象獲得具體方法
<aop:advisor>特殊的切面,只有一個通知和一個切入點
advice-ref通知引用
pointcut-ref切入點引用
增強類
public class MyAdvice implements MethodInterceptor,MethodBeforeAdvice,AfterReturningAdvice {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
/*環繞通知 調用目標方法前後都會執行
* 是否放行(調用)*/
System.out.println("環繞通知 --- 後置通知");
//調用目標方法
Object o = invocation.proceed();
System.out.println("環繞通知 --- 後置通知");
return o;
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("afterReturning 後置通知");
}
@Override
public void before(Method returnValue, Object[] args, Object target) throws Throwable {
System.out.println("前置通知");
}
}
spring-di.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- spring的全自動 -->
<bean id="bookService" class="com.tf.aop.auto.BookServiceImpl"></bean>
<bean id="myAdvice" class="com.tf.aop.auto.MyAdvice"></bean>
<!-- 全自動配置
解析到aop節點 會爲目標類自動創建代理對象那個
-->
<aop:config>
<aop:pointcut expression="execution(* com.tf.aop.auto.*Impl.*(..))" id="mypointcut"/>
<aop:advisor advice-ref="myAdvice" pointcut-ref="mypointcut"/>
</aop:config>
</beans>
Test測試類
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:com/tf/aop/auto/spring-di.xml")
public class TestAuto {
@Resource
private BookService bookService;
@Test
public void test(){
bookService.add();
}
}
7、2 切入點表達式
.execution() 用於描述方法 @annotation() 描述註解
語法:execution(修飾符返回值包.類.方法名(參數) throws異常
修飾符,一般省略
public 公共方法
* 任意
返回值,不能省略
void 返回沒有值
String 返回值字符串
* 任意
包,[省略]
com.qf.crm 固定包
com.qf.crm.*.service crm包下面子包任意包下的service包(例如:com.qf.crm.staff.service)
com.qf.crm.. crm包下面的所有子包(含自己)
com.qf.crm.*.service.. crm包下面任意子包,固定目錄service,service目錄任意包
com.qf.crm.*
類,[省略]
UserServiceImpl 指定類
*Impl 以Impl結尾
User* 以User開頭
* 任意
方法名,不能省略
addUser 固定方法
add* 以add開頭
*Do 以Do結尾
* 任意
(參數)
() 無參
(int) 一個整型
(int ,int) 兩個
(..) 參數任意
throws ,可省略,一般不寫。
比如:execution(* com.tf.aop.auto.*Impl.*(..)) :com.tf.aop.auto包下以Impl結尾的類的任意方法
八、AspectJ介紹
- AspectJ是一個基於Java語言的AOP框架
- Spring2.0以後新增了對AspectJ切點表達式支持
- @AspectJ 是AspectJ1.5新增功能,通過JDK5註解技術,允許直接在Bean類中定義切面
新版本Spring框架,建議使用AspectJ方式來開發AOP
- 主要用途:自定義開發
8、1 AspectJ 通知類型
- aspectj 通知類型,只定義類型名稱。已知方法格式。
- 個數:6種(環繞比較重要)。
before:前置通知(應用:各種校驗)
在方法執行前執行,如果通知拋出異常,阻止方法運行
afterReturning:後置通知(應用:常規數據處理)
方法正常返回後執行,如果方法中拋出異常,通知無法執行
必須在方法執行後才執行,所以可以獲得方法的返回值。
around:環繞通知(應用:十分強大,可以做任何事情)
方法執行前後分別執行,可以阻止方法的執行
必須手動執行目標方法
afterThrowing:拋出異常通知(應用:包裝異常信息)
方法拋出異常後執行,如果方法沒有拋出異常,無法執行
after:最終通知(應用:清理現場)
方法執行完畢後執行,無論方法中是否出現異常
環繞
try{ //前置:before //手動執行目標方法 //後置:afterRetruning } catch(){ //拋出異常 afterThrowing } finally{ //最終 after } |
代碼:
增強類(通知名稱任意(方法名任意)
/*JoinPoint 可以得到目標類的相關信息
* ProceedingJoinPoint 可以控制是否調用目標方法 只能用到環繞通知方法中
* */
public class MyAdvice {
public void before(JoinPoint joinpoint){
System.out.println("前置通知:"+joinpoint.getTarget());
}
public void afterReturn(JoinPoint joinpoint){
System.out.println("後置通知:"+joinpoint.getSignature());
}
public void after(){
System.out.println("最終增強");
}
public void around(ProceedingJoinPoint pjo){
System.out.println("環繞=-=後置通知:"+pjo.getSignature().getName());
try{
Object o =pjo.proceed();
System.out.println("環繞---"+o);
}catch(Throwable e){
e.printStackTrace();
}
System.out.println("環繞===後置通知:"+pjo.getSignature().getName());
}
public void throwsAdvice(Throwable throwable){
System.out.println("異常通知"+throwable.getMessage());
}
}
spring-di.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- aspectj方式實現aop -->
<bean id="bookService" class="aspectj.BookServiceImpl"></bean>
<!-- 配置增強類 -->
<bean id="myAdvice" class="aspectj.MyAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* aspectj.*Impl.*(..))" id="mypointcut"></aop:pointcut>
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="mypointcut"/>
<aop:after method="after" pointcut-ref="mypointcut"/>
<aop:around method="around" pointcut-ref="mypointcut"/>
<aop:after-throwing method="throwsAdvice" pointcut-ref="mypointcut" throwing="throwable"/>
<aop:after-returning method="afterReturn" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>
</beans>
測試類
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:aspectj/spring-di.xml")
public class TestAspectj {
@Autowired
private BookService bookService;
@Test
public void test(){
bookService.add();
}
}
總結:如果是環繞增強的方法必須傳遞參數:ProccedingJoinpoint
IIII.spring的全自動和aspectj方式實現的aop
1)spring全自動的增強類需要實現aop聯盟提供的幾個接口:MethodInterceptor(環繞),MethodBeforeAdvice(前置),AfterReturningAdvice(後置)
spring配置文件使用的是:<aop:advisor>節點配置
2)aspectj方式:
增強類不需要實現任何接口,只需要提供幾個用於增強的方法即可
注意:如果是環繞增強的方法必須傳遞參數:ProccedingJoinpoint
其他增強可傳入JoinPoint參數(aspect包下的),該參數可獲得目標方法的基本信息
在spring配置文件中使用<aop:aspect>節點配置
8、2 基於註解
增強類:
@Aspect //該註解等價於 xml<bean id="myAdvice" class="aspectj.MyAdvice"></bean>
@Component
public class MyAdvice {
@Pointcut(value="execution(* aspectj.annotation.*Impl.*(..))")
public void myPointcut(){
}
@Before(value="myPointcut()")
public void before(JoinPoint joinpoint){
System.out.println("前置通知:"+joinpoint.getTarget());
}
@AfterReturning("myPointcut()")
public void afterReturn(JoinPoint joinpoint){
System.out.println("後置通知:"+joinpoint.getSignature());
}
@After("myPointcut()")
public void after(){
System.out.println("最終增強");
}
@Around("myPointcut()")
public void around(ProceedingJoinPoint pjo){
System.out.println("環繞=-=後置通知:"+pjo.getSignature().getName());
try{
Object o =pjo.proceed();
System.out.println("環繞---"+o);
}catch(Throwable e){
e.printStackTrace();
}
System.out.println("環繞===後置通知:"+pjo.getSignature().getName());
}
//異常:添加:throwing="throwable" 值爲參數名稱
@AfterThrowing(value="myPointcut()",throwing="throwable")
public void throwsAdvice(Throwable throwable){
System.out.println("異常通知"+throwable.getMessage());
}
}
spring-di.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="aspectj.annotation"></context:component-scan>
<!-- aop自動代理
proxy-target-class="false" 默認jdk創建代理對象
true :cglib創建代理對象
-->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
</beans>
測試類:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/aspectj/annotation/spring-di.xml")
public class TestAnnotaiont {
@Resource
private BookService bookService;
@Test
public void test(){
//System.out.println(bookService);
bookService.add();
}
}
BookServiceImpl不要忘記加註解@Service