1. 程式人生 > >手寫 Spring 事務、IOC、DI 和 MVC

手寫 Spring 事務、IOC、DI 和 MVC

for keyset lds 請求 遍歷 nts cts pro del

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 原理

技術分享圖片

執行流程:

  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);

        }
    }
}

手寫 Spring 事務、IOC、DI 和 MVC