代理模式:給一個對象提供一個代理,並由代理對象來控制真實對象的訪問(調用者並不知道真實對象是什麼)。
代理模式分靜態代理和動態代理。這裏只討論動態代理,通俗的講,動態代理就是在不修改代碼的基礎對被代理對象進行方法的加強。html
JDK自帶的動態代理就是基於接口的動態代理,被代理對象至少要實現一個接口,不然就沒法使用代理。底層仍是基於Java的反射來建立代理對象的。
JDK動態代理主要涉及兩個類,java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
。下面來看個例子。java
被代理類的接口IProductspring
public interface IProduct {
void saleProduct(Float money);
}
複製代碼
被代理類Product實現上面這個接口數據庫
public class Product implements IProduct{
@Override
public void saleProduct(Float money) {
System.out.println("賣出一個產品,收到金額:" + money);
}
}
複製代碼
編寫一個客戶端類獲取一個Product的動態代理的對象,來控制被代理對象的訪問。 InvocationHandler的實現主要經過內部類實現(能夠直接使用lambda表達式更簡潔)。編程
public class Client {
public static void main(String[] args){
Product product = new Product();
// 基於接口的動態代理(jdk自帶的)
// proxyProduct就是Proxy.newProxyInstance生成的代理對象,須要強轉爲被代理對象的接口類。
// 參數:
// ClassLoader:代理對象的類加載器。使用和被代理對象相同的類加載器,直接調用getClass().getClassLoader()獲取。
// Class<?>[]:被代理對象的接口的字節碼。直接調用getClass().getInterfaces()獲取就行
// InvocationHandler:進行代碼的加強。直接使用內部類或者new一個InvocationHandler的實現類(在實現類中進行代碼加強)
IProduct proxyProduct = (IProduct) Proxy.newProxyInstance(product.getClass().getClassLoader()
, product.getClass().getInterfaces(), new InvocationHandler() {
/** * 進行代理加強的方法 * 參數: * proxy:當前的代理對象 * method:被代理對象當前執行的方法 * args:被代理對象當前執行的方法的參數 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
// 獲取方法執行的參數
Float money = (Float)args[0];
// 判斷當前方法是不是銷售方法
if ("saleProduct".equals(method.getName())){
// 返回值與被代理對象方法的相同
res = method.invoke(product, money * 0.8f);
}
return res;
}
});
// 調用這個代理對象,執行被代理對象的方法。
proxyProduct.saleProduct(1000f);
}
}
複製代碼
CGLIB動態代理是一個基於ASM字節碼操做框架的代碼生成庫,它被普遍用於基於代理的AOP框架提供方法攔截。
本質上說,對於須要被代理的類,它只是動態的生成一個子類以覆蓋非final的方法(因此它不能代理final類或final方法),同時綁定鉤子回調自定義的攔截器。它比使用Java反射的JDK動態代理方法更快。
CGLIB動態代理的核心是net.sf.cglib.proxy.Enhancer類(用於建立被代理對象子類),net.sf.cglib.proxy.MethodInterceptor是最經常使用的回調類型(它是net.sf.cglib.proxy.Callback的一個子接口)。
使用前須要引入CGLIB的jar依賴。數組
<!--cglib動態代理的依賴-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
複製代碼
下面來看一個示例(被代理對象仍是和上面的相同,這裏實現一個客戶端類)。springboot
public class Client {
public static void main(String[] args){
Product product = new Product();
/** * 基於子類的動態代理(cglib) * Enhancer的create方法,經過Enhancer來建立代理對象,MethodInterceptor來加強方法 * 參數: * Class:被代理對象的字節碼 * Callback:用於加強方法。通常使用這個接口的子接口MethodInterceptor。 */
Product proxyProduct = (Product) Enhancer.create(product.getClass(), new MethodInterceptor() {
/** * 執行被代理對象的任何方法都會通過此方法 * @param proxy 當前代理對象 * @param method 被代理對象要執行的方法 * @param args1 要執行的方法的參數 * 以上三個參數與基於接口的動態代理(jdk)的參數一致 * @param methodProxy 當前執行方法的代理對象 通常使用這個對象來調用原始對象的方法,由於它性能更高 * @return 與被代理對象要執行的方法的返回值一致 * @throws Throwable */
@Override
public Object intercept(Object proxy, Method method, Object[] args1, MethodProxy methodProxy) throws Throwable {
Object res = null;
// 獲取方法執行的參數
Float money = (Float)args1[0];
// 判斷當前方法是不是銷售方法
if ("saleProduct".equals(method.getName())){
// 修改原始方法的參數
args1[0] = money * 0.8f;
// 與JDK動態代理的一個區別,使用MethodProxy對象來調用原始對象的方法
res = methodProxy.invoke(product, args1);
}
return res;
}
});
// 經過代理對象執行被代理對象的方法
proxyProduct.saleProduct(1000f);
}
}
複製代碼
spring AOP是基於動態代理實現的,spring默認使用JDK動態代理實現AOP,也能夠強制使用CGLIB。AOP是IoC的一種補充,IoC不依賴與AOP,可是AOP依賴與IoC。mybatis
Spring 使用 AspectJ 提供的 library 解釋與 AspectJ 5 相同的註釋,用於切入點解析和匹配。可是,AOP 運行時仍然是純粹的 Spring AOP,而且不依賴於 AspectJ 編譯器或編織器。(也就是spring只是利用Aspectj來對切入點進行解析和匹配,真正的AOP的執行仍是使用Spring AOP)。app
在springboot中使用spring AOP很是的方便,只需引入一個spring支持AspectJ的starter依賴便可。框架
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
複製代碼
在spring中也很簡單,在引用spring AOP的基礎上再引入一個aspectjweaver依賴就行。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
複製代碼
在spring中,要使用 Java @Configuration啓用 @AspectJ 支持,還需添加@EnableAspectJAutoProxy註解(在springboot中不用)。
下面來看一個簡單的示例(基於springboot)。
先寫個Service類(包括接口和實現類)。
public interface AccountService {
Account findById(Integer aid);
}
@Service
public class AccountServiceImpl implements AccountService {
private static final Logger log = LoggerFactory.getLogger(AccountServiceImpl.class);
@Override
public Account findById(Integer aid) {
log.info("正在執行方法");
return accountMapper.findById(aid);
}
}
複製代碼
一個配置類,對service中的方法進行切面。
@Aspect // 聲明切面, 這個bean將被spring自動檢測,並用於配置Spring AOP
@Component // 將這個類註冊爲一個bean,交給spring來管理(因此spring AOP依賴於IoC)
public class Logger {
private static org.slf4j.Logger log = LoggerFactory.getLogger(Logger.class);
/** * 通用化切入點表達式,其餘切入點能夠直接引用這個 */
@Pointcut("execution(* tkmybatis.service.impl.*.*(..))")
public void pointCut(){
}
/** * 前置通知,在切入點方法執行以前執行 */
@Before("pointCut()")
public void printLogBefore(){
log.info("我在方法執行前");
}
/** * 後置通知,方法正常返回後執行 */
@AfterReturning("pointCut()")
public void printLogAfterReturning(){
log.info("我在方法執行正常返回後");
}
/** * 異常通知,方法發生異常時執行 */
@AfterThrowing("pointCut()")
public void printLogAfterThrow(){
log.info("我在方法執行拋出異常後");
}
/** * 最終通知,不管方法是否執行正常,它都會在最後執行 */
@After("pointCut()")
public void printLogAfter(){
log.info("我在方法執行最後");
}
@Around("pointCut()")
public Object printAround(ProceedingJoinPoint pjp){
Object res;
try{
Object[] args = pjp.getArgs();
log.info("我在方法執行前");
res = pjp.proceed(args);
log.info("我在方法執行正常返回後2");
return res;
}catch (Throwable t){
log.info("我在方法執行拋出異常後2");
throw new RuntimeException(t);
}finally {
log.info("我在方法執行最後");
}
}
}
複製代碼
對於切入點表達式execution(* tkmybatis.service.impl.*.*(..))
,這裏作一下解釋。
execution表達式原本有三個部分(可見類型 返回值 鏈接點的全限定名),這裏的可見類型能夠省略。
更多高級用法詳見Spring官方文檔 Spring AOP
Spring支持聲明式的事務管理和編程式的事務管理。聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,只要加上註解就能夠得到徹底的事務支持。Spring推薦使用聲明式的事務管理。
而聲明式的事務管理就是基於Spring AOP的,其本質就是對要執行的方法進行攔截,在方法執行前加入一個事務,方法執行完成後根據狀況判斷是提交事務仍是回滾事務(拋出了異常)。
經過org.springframework.transaction.annotation.Isolation 這個枚舉類來指定。
public enum Isolation {
// 這是默認值,表示使用底層數據庫的默認隔離級別。對於MySQL的InnoDB存儲引擎,它是REPEATABLE_READ,其它通常的都是READ_COMMITTED
DEFAULT(-1),
// 跟沒有同樣,幾乎不使用。
READ_UNCOMMITTED(1),
// 只能讀取另外一個事務已提交的事務,能防止髒讀。也是通常數據庫的默認隔離級別。
READ_COMMITTED(2),
// 可重複讀(在一個事務內屢次查詢的結果相同,其它事務不可修改該查詢條件範圍內的數據,至關於加了個讀鎖)
REPEATABLE_READ(4),
// 全部的事務依次逐個執行,至關於串行化了,效率過低,通常也不使用。
SERIALIZABLE(8);
}
複製代碼
若是在開始當前事務以前,一個事務上下文已經存在,此時有若干選項能夠指定一個事務性方法的執行行爲。org.springframework.transaction.annotation.Propagation枚舉類中定義了7表示傳播行爲的枚舉值。
public enum Propagation {
// 若是當前存在事務,則加入該事務;若是當前沒有事務,則建立一個新的事務。
REQUIRED(0),
// 若是當前存在事務,則加入該事務;若是當前沒有事務,則以非事務的方式繼續運行。
SUPPORTS(1),
// 若是當前存在事務,則加入該事務;若是當前沒有事務,則拋出異常。
MANDATORY(2),
// 建立一個新的事務,若是當前存在事務,則把當前事務掛起。
REQUIRES_NEW(3),
// 以非事務方式運行,若是當前存在事務,則把當前事務掛起。
NOT_SUPPORTED(4),
// 以非事務方式運行,若是當前存在事務,則拋出異常。
NEVER(5),
// 若是當前存在事務,則建立一個事務做爲當前事務的嵌套事務來運行;若是當前沒有事務,則該取值等價於REQUIRED。
NESTED(6);
}
複製代碼
聲明式事務能夠直接使用@Transactional註解(這裏只討論這個)來配置,固然也可使用XML配置文件。
先來看一下@Transactional註解都有啥
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// 別名爲transactionManager,其實這兩是同一個。就是事務的名字。
@AliasFor("transactionManager")
String value() default "";
// 事務的傳播行爲,默認值爲REQUIRED
Propagation propagation() default Propagation.REQUIRED;
// 事務的隔離級別,默認爲默認值(也就是使用底層數據庫的隔離級別)
Isolation isolation() default Isolation.DEFAULT;
// 超時時間,默認爲 -1,也就是沒有超時時間。
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
// 是否只讀,默認爲false。
boolean readOnly() default false;
// 會觸發事務回滾的異常的字節碼數組
Class<? extends Throwable>[] rollbackFor() default {};
// 不會觸發事務回滾的異常的字節碼數組
Class<? extends Throwable>[] noRollbackFor() default {};
}
複製代碼
這裏直接在實現類上使用,這個註解還能夠在接口(通常不在接口上使用)和方法上使用(可見性必須爲public,不然會被忽略)。
若是是在spring中使用,須要在配置類中加上@EnableTransactionManagement註解(springboot則不須要)。proxy-target-class屬性能夠控制動態代理的類型,若是值爲false或忽略此屬性,則使用JDK的動態代理,能夠設置爲true以強制使用CGLIB來進行動態代理(若是被代理的類沒有實現接口,將會自動使用CGLIB進行動態代理)。
@Service
@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class) // 這裏觸發回滾的異常設置爲Exception
public class AccountServiceImpl implements AccountService {
}
複製代碼
參考:
CGLIB動態代理介紹
Spring AOP
Spring AOP的使用
Spring 事務管理
Spring 事務