[筆記]架構探險-從零開始寫JavaWeb框架-2.1. 之使框架具有aop特性-aop框架載入與切面執行流程分析
囉嗦一句:本筆記只是自己在學習過程中的一些分析和理解,看的人不一定能看懂.如果有興趣還是去買這本書看.筆記就當是另外一種解說好了
在本章節中會學習到如下的技術:
- 如何理解並使用代理技術
- 如何使用Spring提供的AOP技術(忽略,太多知識)
- 如何使用動態代理技術實現AOP框架
- 如何理解並使用ThreadLocal技術
- 如何理解資料庫事務管理機制
- 如何使用AOp框架實現事務控制
靜態代理
顧名思義,我的理解就是,在編譯前寫死的. 就如同下面這個例子, 寫了一個類.然後實現目標類的所有方法,然後再轉調目標類的方法.
interface IHello{
public void say(String msg);
}
class Hello implements IHello{
public void say(String msg) {
System.out.println(msg);
}
}
// 靜態代理
class HelloProxy implements IHello{
private IHello iHello;
public HelloProxy(IHello iHello) {
this.iHello = iHello;
}
@Override
public void say(String msg) {
System.out.println("增加了前置處理");
iHello.say(msg);
System.out.println("增加了後置處理");
}
}
class Demo{
public static void main(String[] args) {
HelloProxy proxy = new HelloProxy(new Hello());
proxy.say("靜態代理");
}
}
JDK動態代理
在執行期間根據代理類的位元組碼生成代理類的例項.
JDK動態代理:
- 使用Proxy類來獲取代理類
- proxy需要 一個 InvocationHandler 型別的代理實現處理類(就是執行切面邏輯的地方)
- 只能代理 有介面的物件例項.強轉的話,也只能轉換為介面.
/**
* Created by zhuqiang on 2015/10/24 0024.
* jdk 動態代理
*/
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增加了前置處理");
Object result = method.invoke(target, args); //轉調目標的實際處理類
System.out.println("增加了後置處理");
return result;
}
public static void main(String[] args) {
Hello hello = new Hello();
DynamicProxy proxy = new DynamicProxy(hello);
IHello o = (IHello) Proxy.newProxyInstance(
hello.getClass().getClassLoader(), // 類載入器
hello.getClass().getInterfaces(), //該類所實現的介面
proxy); // 代理實現類
o.say("動態代理 簡單使用");
}
}
//
interface IHello{
public void say(String msg);
}
class Hello implements IHello{
public void say(String msg) {
System.out.println(msg);
}
}
CGlib動態代理
/**
* Created by zhuqiang on 2015/10/24 0024.
*/
public class CGlibProxy implements MethodInterceptor {
private static CGlibProxy cp = new CGlibProxy();
public static CGlibProxy getInstance(){
return cp;
}
// 獲取代理類
public <T> T getProxy(Class<T> cls){
return (T)Enhancer.create(cls,this);
}
/**
*
* @param o 目標類
* @param method 目標方法
* @param objects 目標方法引數
* @param methodProxy 方法代理
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("後置");
return result;
}
}
class HelloCg{
public void say(String msg) {
System.out.println(msg);
}
}
class CGlibTest{
public static void main(String[] args) {
HelloCg proxy = CGlibProxy.getInstance().getProxy(HelloCg.class);
proxy.say("cglib 動態代理,支援沒有介面的類哦");
}
}
什麼是AOP
Aop(Aspect Oriented Programming面向方面程式設計) : 是對OOP程式設計的一種補充,也叫面向切面,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術. 由Aspect切面 和 Prointcut切點組成.
AOP框架有 aspectJ,前生是AspectWekz.
- Aspect切面: 就是橫切業務邏輯的程式碼
- Prointcut切點: 用一個條件來匹配在哪些地方使用切面.
Spring AOP
這章節就忽略吧.知識點太多了. 要弄明白,得花一些時間,可時間不多.最重要的你要有個大概的知識體系,到時候用到了,再去深挖. 放上一張書上的 思維導圖.
載入AOP框架
對 切面@Aspect 註解載入的分析;
1. @Aspect 表示一個類是一個切面,
2. 註解接收一個註解型別的class,表示該切面的切點是 被目標註解類修飾的類中的所有方法(連線點)
AopHelper 的最終目標是: 建立 目標類class 與 增強它的 切面代理類 進行關聯
注意:這個操作會讓代理類 覆蓋掉 之前建立的 普通例項.因為HelperLoader中的載入順序是:
ClassHelper.class,
BeanHelper.class,
AopHelper.class,
IocHelper.class,
ControllerHelper.class
通過上面的載入順序可以看到,aop載入的時候,會把之前BeanHelper中把我們需要管理的bean例項化的物件,對應的class例項物件用當前的代理進行替換掉. 那麼這樣一來,在外部獲取到的 目標類其實就已經是一個代理類了
實現的思路:
Map<Class<?>,Set<Class<?>>> createProxyMap()
: 將切面類 和 需要被增強的類class建立關聯; key=切面類 value=所有需要被該切面所增強的類class
思路是:- 通過從ClassHelper中掃描到的class集合中 挑選出AspectProxy的子類(也就是 我們編寫的切面類)
- 找到之後還不行,還需要從中過濾,拿到被Aspect註解所標註的類(被該註解所標註的類的含義在上面有講)
- 拿到 被Aspect標註的類之後,我們就可以拿到 拿到 該註解中的切點,也就是說,我們能拿到所有需要被該切面所增強的類class
- 有類切面類(第2步) , 有了 所有切面類所對應的需要被增強的所有的類,那麼就建立關聯.返回結果
Map<Class<?>,List<Proxy>> createTargetMap
: 根據 createProxyMap 返回的值得,繼續建立關聯,key = 被增強的類class,value=切面列表例項 (因為一個類,可以被多個切面增強)
思路是:- 建立切面類的例項
- 將 該切面例項所對應增強的class建立關聯,因為一個類,可以被多個切面所增強
使用cglib建立代理類,然後把bean容器中的普通示例類給替換掉.
思路是:- 使用Enhancer建立方法級別的攔截增強,
ProxyManger.createProxy(final Class<?> targetClass,final List<Proxy> proxyList)
把目標類和對應增強它的切面列表在intercept中 讓我們的代理鏈模型去執行增強方法,在適合的切入點委託目標方法:return new ProxyChain(targetClass,targetObj,method,methodProxy,paramsObjects,proxyList).doProxyChain();
注意: 一定要注意這裡,最開始一直不知道 這個代理鏈有什麼用處,然後在實現代理的時候 方法攔截器中intercept忘記了 cglib生成代理的 步驟api.直接返回了我們的代理鏈.後來就直接丟擲了 不能轉換的異常.
debug除錯 這個代理鏈的運作流程.發現.其實就是一個 職責鏈模式的設計,當我們代理的方法被攔截的之後,就會執行 用cglib生成代理的時候裡面的程式碼,new ProxyChain(..).doProxyChain(). 這裡是一個代理鏈條的入口. 這個裡面 會判斷,如果 有切面,則執行切面的 doProxy 方法.而 切面的doProxy方法裡面 會在合適的時機(也就是我們定義的前後增強等位置)呼叫切面實現類的增強方法.然後繼續呼叫doProxyChain(),這個時候就相當於 代理連的doProxyChain()方法被切面代理連給遞迴呼叫了.這樣一來, 就會繼續判斷是否還有下一個切面增強類,如此的遞迴呼叫,最後委託目標方法 返回結果.誒.到現在才發現,最開始,就是這個代理鏈,和 增強類怎麼被呼叫的設計是最難的了. 好吧,囉嗦了這麼多話,下面放出一張流程圖.來直觀的感受下,這個代理鏈的增強類是怎麼運作的.
- 使用Enhancer建立方法級別的攔截增強,
/**
* Created by zhuqiang on 2015/10/25 0025.
* 攔截所有的controller方法
* 我們想使用這個切面類 來攔截所有controller的方法.
*/
@Aspect(Controller.class)
public class ControllerAspect extends AspectProxy{
private static final Logger LOGGER = LoggerFactory.getLogger(PropsUtil.class);
private long begin; //開始時間
@Override
public void before(Class<?> cls, Method method, Object[] params) throws Throwable {
begin = System.currentTimeMillis();
LOGGER.debug(String.format("begin:class:%s,method:%s",cls.getName(),method.getName()));
}
@Override
public void after(Class<?> cls, Method method, Object[] params) throws Throwable {
long end = System.currentTimeMillis();
LOGGER.debug(String.format("end:class:%s,method:%s,耗時:%s", cls.getName(), method.getName(),end - begin));
}
}
// 載入aop框架的aophelper
/**
* Created by zhuqiang on 2015/10/26 0026.
*/
public final class AopHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(AopHelper.class);
static {
try {
Map<Class<?>, Set<Class<?>>> proxyMap = createProxyMap();
Map<Class<?>, List<Proxy>> targetMap = createTargetMap(proxyMap);
for(Map.Entry<Class<?>, List<Proxy>> targetEnt:targetMap.entrySet()){
Class<?> targetClass = targetEnt.getKey();
List<Proxy> proxyList = targetEnt.getValue();
Object proxy = ProxyManger.createProxy(targetClass, proxyList);
BeanHelper.setBean(targetClass,proxy);
}
}catch (Exception e){
LOGGER.error("aop fail",e);
}
}
/**
* 獲取 aspect 設定的 註解類,型別的所有class 集合
* @param aspect
* @return
* @throws Exception
*/
private static Set<Class<?>> createTargetClassSet(Aspect aspect) throws Exception{
Set<Class<?>> targetClassSet = new HashSet<>();
Class<? extends Annotation> annotation = aspect.value();
// Aspect 是對一類註解類 進行攔截,這裡是 不為空並且不是 aspect註解,就可以獲取該註解
if(annotation != null && !annotation.equals(Aspect.class)){
targetClassSet.addAll(ClassHelper.getClassSetByAnnotation(annotation));
}
return targetClassSet;
}
/**
* 獲取需要被代理的 所有 class 物件
* @return key= 切面 ,value = 該切面所增強的所有 class 物件
* @throws Exception
*/
private static Map<Class<?>,Set<Class<?>>> createProxyMap() throws Exception{
Map<Class<?>,Set<Class<?>>> proxyMap = new HashMap<>();
// 獲取 所有的切面代理類 的 子類
Set<Class<?>> aspectProxySet = ClassHelper.getClassSetBySuper(AspectProxy.class);
for (Class<?> cls : aspectProxySet) {
if(cls.isAnnotationPresent(Aspect.class)){// 判斷該class 是否有 aspect註解(是否是一個切面標註)
Aspect aspect = cls.getAnnotation(Aspect.class); // 獲取註解類
Set<Class<?>> targetClassSet = createTargetClassSet(aspect); // 由於 aspect 的值是接收一個 註解類,所以這裡是獲取到 使用該註解類的所有class
proxyMap.put(cls,targetClassSet);
}
}
return proxyMap;
}
/**
* 把 class 和 增強它的切面例項進行關聯
* @param proxyMap 切面對應需要增強的class集合.
* @return
* @throws Exception
*/
private static Map<Class<?>,List<Proxy>> createTargetMap(Map<Class<?>,Set<Class<?>>> proxyMap) throws Exception{
Map<Class<?>,List<Proxy>> targetMap = new HashMap<>();
for(Map.Entry<Class<?>,Set<Class<?>>> proxyEnt : proxyMap.entrySet()){
Class<?> proxyClass = proxyEnt.getKey(); // 獲取切面class
Set<Class<?>> targetClassSet = proxyEnt.getValue(); //切面所需要增強的 class 物件(也就是我們需要把該切面在哪些例項上增強的物件class)
for (Class<?> targetClass : targetClassSet) {
Proxy proxy = (Proxy)proxyClass.newInstance(); // 建立該切面的例項
if(targetMap.containsKey(targetClass)){
targetMap.get(targetClass).add(proxy);
}else{
List<Proxy> proxyList = new ArrayList<>();
proxyList.add(proxy);
targetMap.put(targetClass,proxyList);
}
}
}
return targetMap;
}
}
/**
* Created by zhuqiang on 2015/10/25 0025.
* 代理鏈: 可以將多個代理通過一條鏈子串起來,一個一個地去執行,執行順序取決於新增到鏈上的先後順序(這不會是職責鏈模式吧)
*/
public class ProxyChain {
private final Class<?> targetClass; //目標類
private final Object targetObject; //目標物件
private final Method targetMethod; //目標方法
private final MethodProxy methodProxy; //方法代理
private final Object[] methodParams; //方法引數
private List<Proxy> proxyList = new ArrayList<>(); //代理列表
private int proxyIndex = 0; // 代理索引
public ProxyChain(Class<?> targetClass, Object targetObject, Method targetMethod, MethodProxy methodProxy, Object[] methodParams, List<Proxy> proxyList) {
this.targetClass = targetClass;
this.targetObject = targetObject;
this.targetMethod = targetMethod;
this.methodProxy = methodProxy;
this.methodParams = methodParams;
this.proxyList = proxyList;
}
public Class<?> getTargetClass() {
return targetClass;
}
public Method getTargetMethod() {
return targetMethod;
}
public Object[] getMethodParams() {
return methodParams;
}
public Object doProxyChain() throws Throwable{
Object methodResult;
if(proxyIndex < proxyList.size()){ //根據傳進來的 代理列表,
methodResult = proxyList.get(proxyIndex++).doProxy(this);
}else{
methodResult = methodProxy.invokeSuper(targetObject,methodParams);
}
return methodResult;
}
}
/**
* Created by zhuqiang on 2015/10/25 0025.
* 代理介面
*/
public interface Proxy {
/** 執行鏈式代理 */
Object doProxy(ProxyChain proxyChain) throws Throwable;
}
/**
* Created by zhuqiang on 2015/10/25 0025.
* 代理鏈: 可以將多個代理通過一條鏈子串起來,一個一個地去執行,執行順序取決於新增到鏈上的先後順序(這不會是職責鏈模式吧)
*/
public class ProxyChain {
private final Class<?> targetClass; //目標類
private final Object targetObject; //目標物件
private final Method targetMethod; //目標方法
private final MethodProxy methodProxy; //方法代理
private final Object[] methodParams; //方法引數
private List<Proxy> proxyList = new ArrayList<>(); //代理列表
private int proxyIndex = 0; // 代理索引
public ProxyChain(Class<?> targetClass, Object targetObject, Method targetMethod, MethodProxy methodProxy, Object[] methodParams, List<Proxy> proxyList) {
this.targetClass = targetClass;
this.targetObject = targetObject;
this.targetMethod = targetMethod;
this.methodProxy = methodProxy;
this.methodParams = methodParams;
this.proxyList = proxyList;
}
public Class<?> getTargetClass() {
return targetClass;
}
public Method getTargetMethod() {
return targetMethod;
}
public Object[] getMethodParams() {
return methodParams;
}
public Object doProxyChain() throws Throwable{
Object methodResult;
if(proxyIndex < proxyList.size()){ //根據傳進來的 代理列表,
methodResult = proxyList.get(proxyIndex++).doProxy(this);
}else{
methodResult = methodProxy.invokeSuper(targetObject,methodParams);
}
return methodResult;
}
}
/**
* Created by zhuqiang on 2015/10/25 0025.
* 代理管理器,用來建立代理物件
*/
public class ProxyManger {
/**
* 建立代理
* @param targetClass 目標類
* @param proxyList 代理列表
* @param <T>
* @return
*/
public static <T>T createProxy(final Class<?> targetClass,final List<Proxy> proxyList){
return (T)Enhancer.create(targetClass, new MethodInterceptor() {
@Override
public Object intercept(Object targetObj, Method method, Object[] paramsObjects, MethodProxy methodProxy) throws Throwable {
return new ProxyChain(targetClass,targetObj,method,methodProxy,paramsObjects,proxyList).doProxyChain();
}
});
}
}
最後說一句: 上面的思路 也分析了.載入aop比較簡單.學過一次後有思路了 基本上向這樣簡單的aop框架都能弄出來. 我覺得難的點在於, 這個代理鏈的執行這一個邏輯模版框架的設計, 和 動態代理的建立. 現在還是覺得設計模式有必要學習.
待續..待學習