手寫 Spring 事務、IOC、DI 和 MVC

進擊的java程式設計師k發表於2019-05-08

Spring AOP 原理

什麼是 AOP?

AOP 即面向切面程式設計,利用 AOP 可以對業務進行解耦,提高重用性,提高開發效率

應用場景:日誌記錄,效能統計,安全控制,事務處理,異常處理

AOP 底層實現原理是採用代理實現的

Spring 事務

基本特性:

  • 原子性
  • 隔離性
  • 一致性
  • 永續性

事務控制分類:

程式設計式事務:手動控制事務操作

宣告式事務:通過 AOP 控制事務

程式設計式事務實現

使用程式設計事務實現手動事務

@Component
@Scope("prototype")
public class TransactionUtils {

    // 獲取事務源
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    // 開啟事務
    public TransactionStatus begin() {
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
        return transaction;
    }

    // 提交事務
    public void commit(TransactionStatus transaction) {
        dataSourceTransactionManager.commit(transaction);
    }

    // 回滾事務
    public void rollback(TransactionStatus transaction) {
        dataSourceTransactionManager.rollback(transaction);
    }
}複製程式碼

AOP技術封裝手動事務

@Component
@Aspect
public class TransactionAop {
    @Autowired
    private TransactionUtils transactionUtils;

    @Around("execution(* com.kernel.service.UserService.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) {

        try {
            // 呼叫方法之前執行
            System.out.println("開啟事務");
            TransactionStatus transactionStatus = transactionUtils.begin();
            proceedingJoinPoint.proceed();
            System.out.println("提交事務");
            transactionUtils.commit(transactionStatus);
        } catch (Throwable throwable) {
            System.out.println("回滾事務");
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }
}複製程式碼

事務注意事項:

一定不要將程式碼通過 try 包裹起來,如果程式發生異常,事務接收不到異常,就會認為程式正常執行,就不會進行回滾,必須手動回滾

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

宣告式事務

通過 AOP 實現,對方法進行攔截,在方法執行之前開啟事務,結束後提交事務,發生異常回滾事務

自定義事務註解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtTransaction {

}複製程式碼

事務實現

@Component
@Aspect
public class TransactionAop {
    @Autowired
    private TransactionUtils transactionUtils;

    private TransactionStatus transactionStatus = null;

    /**
     * AOP實現事務管理
     *
     * @param proceedingJoinPoint 切面通知物件
     */
    @Around("execution(* com.kernel.service.*.* (..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint)  {
        try {
            // 獲取註解物件
            ExtTransaction extTransaction = getExtTransaction(proceedingJoinPoint);
            begin(extTransaction);
            // 執行目標方法
            proceedingJoinPoint.proceed();
            // 提交事務
            commit();
        } catch (Throwable throwable) {
            transactionUtils.rollback();
        }
    }

    /**
     * 獲取註解物件
     *
     * @param proceedingJoinPoint 切面通知物件
     * @return 註解物件
     * @throws NoSuchMethodException
     */
    public ExtTransaction getExtTransaction(ProceedingJoinPoint proceedingJoinPoint) throws NoSuchMethodException {
        // 獲取方法名稱
        String method = proceedingJoinPoint.getSignature().getName();
        // 獲取目標方法
        Class<?> classTarget = proceedingJoinPoint.getTarget().getClass();
        // 獲取目標物件型別
        Class[] parameterTypes = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterTypes();
        // 獲取目標物件方法
        Method objMethod = classTarget.getMethod(method, parameterTypes);
        // 獲取註解
        ExtTransaction declaredAnnotation = objMethod.getDeclaredAnnotation(ExtTransaction.class);
        return declaredAnnotation;
    }

    /**
     * 開啟事務
     * @param extTransaction 註解物件
     * @return 事務物件
     */
    TransactionStatus begin(ExtTransaction extTransaction) {
        if (extTransaction != null)
            transactionStatus = transactionUtils.begin();
        return transactionStatus;
    }

    /**
     * 提交事務
     */
    void commit() {
        if (transactionStatus != null)
            transactionUtils.commit(transactionStatus);
    }

    /**
     * 回滾事務
     */
    void rollback() {
        transactionUtils.rollback();
    }
}複製程式碼

Spring事物傳播行為

  • PROPAGATION_REQUIRED:如果當前有事務,就用當前事務,如果當前沒有事務,就新建一個事務
  • PROPAGATION_SUPPORTS:支援當前事務,如果當前沒有事務,就以非事務方式執行
  • PROPAGATION_MANDATORY:支援當前事務,如果當前沒有事務,就丟擲異常
  • PROPAGATION_REQUIRES_NEW:新建事務,如果當前存在事務,把當前事務掛起
  • PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起
  • PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常

什麼是 Spring IOC?

Spring IOC 指的是控制反轉,IOC 容器負責例項化、定位、配置應用程式中的物件及建立這些物件間的依賴,交由Spring來管理這些,實現解耦

手寫 Spring IOC

實現步驟:

掃包

將標註了註解的類,通過反射建立例項並新增的 bean 容器中

當使用者向容器要 bean 時,通過 beanId 在 bean 容器中查詢並返回例項

package com.kernel.ext;

import com.kernel.ext.annotation.ExtAutoWired;
import com.kernel.ext.annotation.ExtService;
import com.kernel.utils.ClassUtil;
import org.apache.commons.lang.StringUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * IOC 註解版本
 */
public class ExtClassPathXmlApplicationContext {
    // 包名
    private String packageName;
    // bean容器
    private ConcurrentHashMap<String, Object> beans = null;

    /**
     * 建構函式
     *
     * @param packageName 包名
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public ExtClassPathXmlApplicationContext(String packageName) throws InstantiationException, IllegalAccessException {
        this.packageName = packageName;
        init();
    }

    /**
     * 初始化物件
     *
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private void init() throws IllegalAccessException, InstantiationException {
        // 遍歷所有類
        List<Class<?>> classes = ClassUtil.getClasses(packageName);

        // 將所有標註ExtService註解的類加入到容器中
        findAnnotationByClasses(classes);
    }

    /**
     * 過濾標註ExtService註解的類
     *
     * @param classes
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private void findAnnotationByClasses(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {
        for (Class classInfo : classes) {
            ExtService extService = (ExtService) classInfo.getAnnotation(ExtService.class);
            if (extService != null) {
                Object newInstance = newInstance(classInfo);
                beans.put(toLowerCaseFirstOne(classInfo.getSimpleName()), newInstance);
            }
        }
    }

    /**
     * 通過反射構建物件
     *
     * @param classInfo
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private Object newInstance(Class classInfo) throws InstantiationException, IllegalAccessException {
        return classInfo.getClass().newInstance();
    }

    /**
     * 通過beanId查詢對應的例項
     *
     * @param beanId
     * @return
     */
    public Object getBean(String beanId) throws IllegalAccessException {
        Object object = null;
        if (StringUtils.isEmpty(beanId))
            return null;
        for (String id : beans.keySet())
            if (beanId.equals(id)) {
                object = beans.get(beanId);
                attrAssign(object);
                break;
            }
        return object;
    }

    /**
     * 依賴注入
     */
    void attrAssign(Object object) throws IllegalAccessException {
        Class<?> aClass = object.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field : declaredFields) {
            ExtAutoWired extAutoWired = field.getAnnotation(ExtAutoWired.class);
            if (extAutoWired != null) {
                field.setAccessible(true);
                Object bean = getBean(field.getName());
                field.set(field.getName(), object);
            }
        }
    }

    /**
     * 首字母變小寫
     *
     * @param s
     * @return
     */
    public static String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0)))
            return s;
        else {
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append(Character.toLowerCase(s.charAt(0)));
            stringBuffer.append(s.substring(1));
            return stringBuffer.toString();
        }
    }
}複製程式碼

Spring MVC 原理

手寫 Spring 事務、IOC、DI 和 MVC

執行流程:

  1. 使用者請求 url 至前端控制器 DispatcherServlet

  2. DispatcherServlet 呼叫處理器對映器 HandlerMapping

  3. HandlerMapping 根據 url 找到具體的處理器生成處理器執行鏈,並將執行鏈返回給 DispatcherServlet

  4. DispatcherServlet 根據處理器 Handler 獲取處理器介面卡 HandlerAdapter 執行

  5. 執行 Handler

  6. 返回 ModelAndView 返回給 DispatcherServlet

  7. DispatcherServlet 將 ModelAnd view 傳遞給檢視解析器 ViewResolver

  8. ViewResolver 解析成具體 View

  9. 渲染檢視

  10. 響應頁面給使用者

Servlet 生命週期

init:在 Servlet 生命週期中,該方法僅執行一次,它是在將伺服器裝入 Servlet 時執行的,負責初始化 Servlet 物件,Servlet 是單例多執行緒的

service:負責響應請求,每當一個客戶請求一個 HttpServlet 物件,該物件的 Service 方法就要被呼叫,傳遞一個 ServletRequest 和 ServletResponse 物件

destroy:在伺服器停止解除安裝 Servlet 時呼叫

手寫 Spring MVC

實現步驟:

建立一個 ExtDispatcherServlet 繼承 HttpServlet

掃包

將標註了 @ExtController 註解的類,通過反射建立物件新增到容器中,將 beanId 和控制器關聯

將標註了 @ExtRequestMapping 註解的類,將請求url 和控制器物件關聯,將 url 和 方法關聯

當使用者請求 url 時,查詢和 url 對應的物件,然後查詢和 url 對應的方法,執行方法,解析並渲染

package com.kernel.ext.servlet;

import com.kernel.controller.ExtIndexController;
import com.kernel.ext.annotation.ExtController;
import com.kernel.ext.annotation.ExtRequestMapping;
import com.kernel.utils.ClassUtil;
import org.apache.commons.lang.StringUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 手寫SpringMVC
 */
public class ExtDispatcherServlet extends HttpServlet {
    // 關聯beanId和Object
    private ConcurrentHashMap<String, Object> mvcBeans = new ConcurrentHashMap<>();
    // 關聯url和控制器物件
    private ConcurrentHashMap<String, Object> mvcBeanUrl = new ConcurrentHashMap<>();
    // 關聯url和methodName
    private ConcurrentHashMap<String, String> mvcMethodUrl = new ConcurrentHashMap<>();

    /**
     * 初始化Servlet
     */
    public void init() {
        try {
            List<Class<?>> classes = ClassUtil.getClasses("com.kernel.controller");
            findClassMVCBeans(classes);
            handlerMapping(mvcBeans);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 關聯url和控制器物件、url和methoName
     * @param mvcBeans
     */
    private void handlerMapping(ConcurrentHashMap<String, Object> mvcBeans) {
        for (Object classInfo : mvcBeans.values()) {
            ExtRequestMapping extCla***equestMapping = classInfo.getClass().getDeclaredAnnotation(ExtRequestMapping.class);
            String requestBaseUrl = null;
            if (extCla***equestMapping != null) {
                requestBaseUrl = extCla***equestMapping.value();
            }
            Method[] methods = classInfo.getClass().getDeclaredMethods();
            for (Method method : methods) {
                ExtRequestMapping extMthodRequestMapping = method.getDeclaredAnnotation(ExtRequestMapping.class);
                if (extCla***equestMapping != null){
                    String httpRequestUrl = extMthodRequestMapping.value();
                    mvcBeanUrl.put(requestBaseUrl + httpRequestUrl, classInfo);
                    mvcMethodUrl.put(requestBaseUrl + httpRequestUrl, method.getName());
                }
            }
        }

    }

    /**
     * 將所有控制器新增到mvcBeans中
     * @param classes 包內所有類
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws ClassNotFoundException
     */
    private void findClassMVCBeans(List<Class<?>> classes) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        for (Class classInfo : classes) {
            ExtController extController = (ExtController) classInfo.getDeclaredAnnotation(ExtController.class);
            if (extController != null){
                mvcBeans.put(classInfo.getName(), ClassUtil.newInstance(classInfo));
            }
        }
    }

    /**
     * get請求
     * @param req
     * @param resp
     * @throws IOException
     * @throws ServletException
     */
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        try {
            doPost(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * post請求
     * @param req
     * @param resp
     */
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 路由分發
     * @param req
     * @param resp
     * @throws Exception
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String requestUrl = req.getServletPath();
        Object object = mvcBeanUrl.get(requestUrl);
        if (object == null)
            object = ExtIndexController.class.newInstance();
        String methodName = mvcMethodUrl.get(requestUrl);
        if (StringUtils.isEmpty(methodName))
            methodName = "error";
        Class<?> classInfo = object.getClass();
        String resultPage = (String) methodInvoke(classInfo, object, methodName);
        viewDisplay(resultPage, req, resp);
    }

    /**
     * 檢視渲染
     * @param resultPage
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    private void viewDisplay(String resultPage, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String suffix = ".jsp";
        String prefix = "/";
        req.getRequestDispatcher(prefix + resultPage + suffix).forward(req, resp);
    }

    /**
     * 反射執行方法
     * @param classInfo 控制器
     * @param object 控制器物件
     * @param methodName 方法名稱
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     */
    private Object methodInvoke(Class<?> classInfo, Object object, String methodName) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Method method = null;
        try {
            method = classInfo.getDeclaredMethod(methodName);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        finally {
            return method.invoke(object);

        }
    }
}複製程式碼

對文章感興趣的朋友可以關注小編,以後會有更多的技術文章輸出



相關文章