動態代理 特點:位元組碼隨用隨創建,隨用隨載入 作用:不修改源碼的基礎上對方法增強 分類: 基於介面的動態代理 基於子類的動態代理 基於介面的動態代理: 涉及的類:Proxy 如何創建代理對象: 使用Proxy類中的newProxyInstance方法 創建代理對象的要求:被代理的類最少實 ...
動態代理**
特點:位元組碼隨用隨創建,隨用隨載入
作用:不修改源碼的基礎上對方法增強
分類:
基於介面的動態代理
基於子類的動態代理
基於介面的動態代理:
涉及的類:Proxy
如何創建代理對象:
使用Proxy類中的newProxyInstance方法
創建代理對象的要求:被代理的類最少實現一個介面,如果沒有則不能使用
newProxyInstance方法的參數:
ClassLoader :類載入器,他是用於載入代理對象位元組碼的,和被代理使用相同的類載入器
Class[] :位元組碼數組:他是用於讓代理對象和被代理對象有相同方法
InvocationHandler:用於提供增強的代碼,他是讓我們寫如何代理,一般都是些一個該介面的實現類,通常情況下都是匿名內部類
package com.itheima;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
producer.saleProduct(10000f);
Iproducer proxyProducer = (Iproducer)Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
//該方法的作用,具有攔截功能
//執行被代理對象的任何介面方法都會經過該方法
//proxy:代理對象的引用
//Method:當前執行的方法
//args:當前執行方法所需的參數
//return:和被代理對象方法有相同的返回值
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增強的代碼
Object returnValue = null;
//獲取方法執行的參數
Float money = (Float)args[0];
//判斷當前方法
if("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
}
基於子類的動態代理
需要導入cglib包
涉及的類:Enhancer
如何創建代理對象:使用Enhancer類中的create方法
創建代理對象的要求:被代理類不能是最終類
create方法的參數
Class:位元組碼,用於指定被代理對象的位元組碼
Callback:用於提供增強的代碼,一般寫的都是該介面的子介面實現類,MethodInterceptor
package com.itheima.cglib;
import com.itheima.Iproducer;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
producer.saleProduct(10000f);
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//提供增強的代碼
Object returnValue = null;
//獲取方法執行的參數
Float money = (Float)objects[0];
//判斷當前方法
if("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(10000f);
}
}
AOP:全稱是Aspect Oriented Programming 面向切麵編程
把我們程式重覆的代碼抽取出來,在需要執行的時候,使用動態代理的技術,在不修改源碼的基礎上,對我們的已有方法進行增強
AOP相關術語
Joinpoint(連接點):所謂連接點是值那些被攔截到的點,在spring中,這些點指的是方法,因為spring只支持方法類型的連接點
pointcut(切入點):所謂切入點是指我們要對哪些JoinPoint進行攔截的定義(被增強的方法才是切入點)
Advice(通知/增強):所謂的通過指的是攔截到Joinpoint之後所要做的事情就是通知,通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知
Target(目標對象):代理的目標對象
Weaving(織入):指的是把增強應用到目標對象來創建新的代理對象的過程
Proxy(代理):一個類被AOP織入增強後,就產生一個結果代理類
Aspect(切麵):是切入和通知的結合
spring基於XML的AOP
1.導入兩個坐標
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>day03_eesy_03springAOP</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
</project>
2.創建業務層和dao層
package com.itheima.service.impl;
import com.itheima.service.IAccountService;
//賬戶業務層實現類
public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("執行了保存");
}
public void updateAccount(int i) {
System.out.println("執行了更新");
}
public int deleteAccount() {
System.out.println("執行了刪除");
return 0;
}
}
3.準備一個具有公共代碼的類
package com.itheima.utils;
//用於記錄日誌的工具類,他裡面提供了公共的代碼
public class Logger {
//用於列印日誌:計劃讓其在切入點方法執行之前執行(切入點方法就是業務層方法)
public void printLog(){
System.out.println("Logger類中的方法開始記錄日誌了");
}
}
目的:在執行業務層的方法之前,先執行printLog方法,通過spring配置的方式來完成,不使用動態代理
4.配置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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的Ioc,把service對象配置進來-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--spring中基於xml的AOP配置步驟
1.把通知Bean也交給spring來管理
2.使用aop:config標簽表名開始AOP的配置
3.使用aop:aspect標簽表明配置切麵
id屬性:是給切麵提供一個唯一標識
ref屬性:是指定通知類bean的Id
4.在aop:aspect標簽的內部使用對象標簽來配置通知的類型
我們現在的示例是讓printLog方法在切入點方法執行之前執行,所以是前置通知
method屬性:用於指定Logger類中哪個方法是前置通知
pointcut屬性:用於指定切入點表達式,該表達式含義指的是對業務層中哪些方法增強
切入點表達式的寫法:
關鍵字:execution(表達式)
表達式:訪問修飾符 返回值 包名.包名.包名....類名.方法(參數列表)
-->
<!--配置Logger類-->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切麵-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知類型,並且建立通知方法和切入點方法的關聯-->
<aop:before method="printLog" pointcut="execution(public void com.itheima.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
5.測試類
package com.ithei.test;
import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
//測試AOP的配置
public class AOPTest {
public static void main(String[] args) {
//1.獲取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.獲取UI小
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.執行方法
as.saveAccount();
}
}
控制台輸出:
Logger類中的方法開始記錄日誌了
執行了保存
切入點表達式: execution(* com.itheima.service.impl..(..))
四種通知的配置
<!--配置AOP-->
<aop:config>
<!--配置切麵-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知類型,並且建立通知方法和切入點方法的關聯-->
<!--前置通知-->
<aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
<!--後置通知-->
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-returning>
<!--異常通知-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-throwing>
<!--最終通知-->
<aop:after method="afterPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after>
</aop:aspect>
</aop:config>