美文网首页
框架相关

框架相关

作者: 万福来 | 来源:发表于2020-05-12 11:06 被阅读0次

    框架相关

    在Spring IOC容器的设计中,有两个主要的容器系列,一个是是实现了BeanFactory接口的简单容器系列;
    这系列容器只实现了容器的最基本功能;另一个是ApplicationContext应用上下文,它是容器的高级形态,应用上下文在简单容器的基础上,增加了许多面向框架的特性,同时对应用环境做了许多适配。

    IOC容器的工作原理

    • BeanDefiniton定位。对于IOC容器来说,它为管理对象之间的依赖关系提供了帮助;
      第一步就是使用Resource接口来统一定位这些BeanDefinition资源,常用的有XML文件配置信息;

    • 容器的初始化。在使用上下文时,需要一个初始化的过程,这个过程的入口实在refresh中实现的,
      初始化过程最重要的就是对步骤一中的BeanDefintion资源进行载入,解析和注册操作,其实就是将BeanDefinition资源转化为容器中的一个HashMap结构进行存储,方便后续的管理和维护。

    在容器初始化操作完成后,IOC容器就可以对外使用了,但这时IOC容器内部只是维护了BeanDefinition,
    还没有生成具体的Bean和依赖注入。

    依赖注入的工作原理

    在客户第一次向容器getBean时,IOC容器就会对相关Bean依赖关系进行注入,如果涉及到的依赖关系也没有初始化完成,则容器会直接递归调用getBean直到该Bean所有的依赖Bean都初始化后,所有的依赖对象注入完成后,然后将创建好的Bean返回给客户。
    如果Bean配置的lazy-init=false,则会在容器初始化之后,直接遍历所有的BeanDefinition判断lazy-init属性值,如果没有启用懒加载,则直接触发getBean方法,在容器初始化完成后,直接加载这些Bean。

    什么是循环依赖?

    循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:


    image.png

    注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。
    Spring中循环依赖场景有:

    • 构造器的循环依赖
    • field属性的循环依赖。

    怎么检测是否存在循环依赖

    检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

    Spring怎么解决循环依赖

    Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或则属性是可以延后设置的(但是构造器必须是在获取引用之前)。
    Spring的单例对象的初始化主要分为三步:

    1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
    2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
    3. initializeBean:调用spring xml中的init 方法。

    从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二部。也就是构造器循环依赖和field循环依赖。
    那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
    首先我们看源码,三级缓存主要指:

    /** Cache of singleton objects: bean name --> bean instance */
        private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
        /** Cache of singleton factories: bean name --> ObjectFactory */
        private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
        /** Cache of early singleton objects: bean name --> bean instance */
        private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
        /** Set of registered singletons, containing the bean names in registration order */
        private final Set<String> registeredSingletons = new LinkedHashSet<String>(256);
        /** Names of beans that are currently in creation */
        private final Set<String> singletonsCurrentlyInCreation =
                Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
    
    

    这三级缓存分别指:
    singletonObjects:单例对象的cache
    earlySingletonObjects :提前暴光的单例对象的Cache
    singletonFactories : 单例对象工厂的cache
    registeredSingletons :保存已经注册的单例对象名称
    singletonsCurrentlyInCreation :保存当前正在创建过程中的单例对象bean名称,可以用来判断当前bean是否在创建过程中,用于后面isSingletonCurrentlyInCreation判断
    在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
    

    上面的代码需要解释两个参数:
    isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
    allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
    分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:

    this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
    

    从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
    从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:

    public interface ObjectFactory<T> {
        T getObject() throws BeansException;
    }
    

    这个接口在下面被引用

    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }
    

    这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

    这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

    知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
    结论,如果要使用循环依赖,不要使用构造方法注入,可以采用属性注入的方式解决。

    什么是AOP编程

    AOP: Aspect Oriented Programming 面向切面编程。   面向切面编程(也叫面向方面):Aspect Oriented Programming(AOP),是目前软件开发中的一个热点。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。   AOP是OOP的延续,是(Aspect Oriented Programming)的缩写,意思是面向切面(方面)编程。   主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。   主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改 变这些行为的时候不影响业务逻辑的代码。可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。

    AOP实现原理

    Spring AOP定义

    • Advice通知,定义在连接点做什么,为切面增强提供织入接口。主要有BeforeAdvice,定义了在目标方法只执行前要做的事情,AfterAdvice定义了目标方法返回后要做什么,ThrowsAdvice定义了目标方法执行异常时要做什么。
    • Pointcut 切点,决定Advice通知作用于哪个连接点,其实就是通过Pointcut来定义需要增强的方法集合,这些集合的选取按照一定的规则来完成。spring提供了正则通配符方式,注解方式来选择切点。
    • Advisor通知器,通知器就是将上面定义的Advice和Pointcut结合起来,将Advice方法织入到Pointcut对应的方法集合中,结合spring的IOC功能,创建这些方法对应的代理对象

    代理设计模式

    代理模式是通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用处理,或调用后处理。既(AOP微实现) ,AOP核心技术面向切面编程。

    代理模式应用场景

    SpringAOP、事物原理、日志打印、权限控制、远程调用、安全代理 可以隐蔽真实角色

    代理的分类

    静态代理(静态定义代理类)
    动态代理(动态生成代理类)

    Jdk自带动态代理

    Cglib 、javaassist(字节码操作库)

    静态代理

    什么是静态代理
    由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

    JDK动态代理

    • 原理:是根据类加载器和接口创建代理类(此代理类是接口的实现类,所以必须使用接口 面向接口生成代理,位于java.lang.reflect包下)
    • 实现方式:通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(…);
      通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
      通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
      通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
    • 缺点:jdk动态代理,必须是面向接口,目标业务类必须实现接口
    // 每次生成动态代理类对象时,实现了InvocationHandler接口的调用处理器对象 
    public class InvocationHandlerImpl implements InvocationHandler {
        private Object target;// 这其实业务实现类对象,用来调用具体的业务方法
        // 通过构造函数传入目标对象
        public InvocationHandlerImpl(Object target) {
            this.target = target;
        }
    ​
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = null;
            System.out.println("调用开始处理");
            result = method.invoke(target, args);
            System.out.println("调用结束处理");
            return result;
        }
    ​
        public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
                IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            // 被代理对象
            IUserDao userDao = new UserDao();
            InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDao);
            ClassLoader loader = userDao.getClass().getClassLoader();
            Class<?>[] interfaces = userDao.getClass().getInterfaces();
            // 主要装载器、一组接口及调用处理动态代理实例
            IUserDao newProxyInstance = (IUserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl);
            newProxyInstance.save();
        }
    ​
    }
    

    CGLIB动态代理

    原理:利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
    什么是CGLIB动态代理
    使用cglib[Code Generation Library]实现动态代理,并不要求委托类必须实现接口,底层采用asm字节码生成框架生成代理类的字节码

    public class CglibProxy implements MethodInterceptor {
        private Object targetObject;
        // 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
        public Object getInstance(Object target) {
            // 设置需要创建子类的类
            this.targetObject = target;
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(target.getClass());
            enhancer.setCallback(this);
            return enhancer.create();
        }
    ​
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("invoke before");
            Object result = proxy.invoke(targetObject, args);
            System.out.println("invoke after");
            // 返回代理对象
            return result;
        }
        public static void main(String[] args) {
            CglibProxy cglibProxy = new CglibProxy();
            UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDao());
            userDao.save();
        }
    }
    

    CGLIB动态代理与JDK动态区别

    java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
    而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

    Spring中用法

    • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
    • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
    • 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
      JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。 CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。 因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态。

    SpringMVC介绍

    SpringMVC是Spring提供给WEB应用的MVC框架,MVC框架一般来说由三部分组成:
    Model:模型层,一般由java bean完成,主要是进行数据库操作;
    View:视图层,用于前端展示,比如jsp、html等;
    Controller:控制层,链接前后端,处理用户请求,起着承前启后的作用;
    早期的MVC模式中,Model由java bean扮演,View由jsp扮演,Controller由Servlet扮演,三者组成一个三角形的MVC框架,使得前后端有了一定的分离,而且控制器和模型层的分离使得很多java代码得以重用,但是随着前端设备的多样化和前端各种技术的兴起,前端和后段的交互只变成json数据的交互,导致后端对于jsp的依赖减少,这就出现了Spring MVC的架构。它的最大特点是结构比较松散,在Spring MVC中能够使用JSP、JSON、XML、PDF等各类视图,所以它能满足手机、PC、平板等不同设备中的WEB请求。

    SrpingMVC初始化过程

    springmvc的核心是DispatcherServlet,它是前端控制器,负责拦截客户端发过来的请求,然后解析请求进行分发。DispatcherServlet是基于Servlet接口开发的,所以使用springmvc先在web.xml中配置DispatcherServlet;每个Servlet在第一次加载时都会调用其init()方法,但是DispatcherServlet本身没有这个方法,所以系统会去它父类寻找init()方法,最后在父类HttpServletBean找到并调用。

    @Override
        public final void init() throws ServletException {
            // Set bean properties from init parameters.
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            if (!pvs.isEmpty()) {
                try {
                    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                    ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                    bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                    initBeanWrapper(bw);
                    bw.setPropertyValues(pvs, true);
                }
                catch (BeansException ex) {
                    if (logger.isErrorEnabled()) {
                        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                    }
                    throw ex;
                }
            }
            // 让子类去实现类似初始化的操作,这就是模版方法模式的使用吧
            initServletBean();
        }
    

    下面是子类org.springframework.web.servlet.FrameworkServlet#initServletBean的源码

    @Override
        protected final void initServletBean() throws ServletException {
            getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
            if (logger.isInfoEnabled()) {
                logger.info("Initializing Servlet '" + getServletName() + "'");
            }
            long startTime = System.currentTimeMillis();
            try {
                          //开始初始化web应用上下文
                this.webApplicationContext = initWebApplicationContext();
                initFrameworkServlet();//空方法,可以留给子类进行扩展
            }
            catch (ServletException | RuntimeException ex) {
                logger.error("Context initialization failed", ex);
                throw ex;
            }
                  //other code...
        }
    
    /**
         * This implementation calls {@link #initStrategies}.
         */
        @Override
        protected void onRefresh(ApplicationContext context) {
            initStrategies(context);
        }
    
        /**
         * Initialize the strategy objects that this servlet uses.
         * <p>May be overridden in subclasses in order to initialize further strategy objects.
         */
        protected void initStrategies(ApplicationContext context) {
            initMultipartResolver(context);
            initLocaleResolver(context);
            initThemeResolver(context);
            initHandlerMappings(context);
            initHandlerAdapters(context);
            initHandlerExceptionResolvers(context);
            initRequestToViewNameTranslator(context);
            initViewResolvers(context);
            initFlashMapManager(context);
        }
    

    总结springMVC初始化过程

    web应用启动时扫描web.xml文件,扫描到DispatcherServlet,对其进行初始化
    调用DispatcherServlet父类的父类HttpServletBean的init()方法,把配置DispatcherServlet的初始化参数设置到DispatcherServlet中,调用子类FrameworkServlet的initServletBean()方法
    initServletBean()创建springMVC容器实例并初始化容器,并且和spring父容器进行关联,使得mvc容器能访问spring容器里面的bean,之后调用子类DispatcherServlet的onRefresh(ApplicationContext context)方法,onRefresh(ApplicationContext context)进行DispatcherServlet的策略组件初始化工作,url映射初始化,文件解析初始化,运行适配器初始化等等。

    Servlet的作用:

    • HttpServletBean:主要做一些初始化的工作,将web.xml中配置的参数设置到Servlet中。比如servlet标签的子标签init-param标签中配置的参数。
    • FrameworkServlet:将Servlet与Spring容器上下文关联。其实也就是初始化FrameworkServlet的属性webApplicationContext,这个属性代表SpringMVC上下文,它有个父类上下文,既web.xml中配置的ContextLoaderListener监听器初始化的容器上下文。
    • DispatcherServlet:初始化各个功能的实现类。比如异常处理、视图处理、请求映射处理等。

    SpringMVC执行源代码

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            HttpServletRequest processedRequest = request;
            HandlerExecutionChain mappedHandler = null;
            boolean multipartRequestParsed = false;
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;
                try {
                    processedRequest = checkMultipart(request);
                    multipartRequestParsed = (processedRequest != request);
                    // 根据当前请求查找HandlerExecutionChain 
                    mappedHandler = getHandler(processedRequest);
                    if (mappedHandler == null) {
                                              //没有找到直接返回no mapping错误
                        noHandlerFound(processedRequest, response);
                        return;
                    }
                    // Determine handler adapter for the current request.
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                    // Process last-modified header, if supported by the handler.
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                                    //进行请求方式检查
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
                                  //执行拦截器链的applyPreHandle
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    applyDefaultViewName(processedRequest, mv);
                                  //执行拦截器链applyPostHandle
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                }
                catch (Exception ex) {
                    dispatchException = ex;
                }
                catch (Throwable err) {
                    // As of 4.3, we're processing Errors thrown from handler methods as well,
                    // making them available for @ExceptionHandler methods and other scenarios.
                    dispatchException = new NestedServletException("Handler dispatch failed", err);
                }
                            //视图渲染完成后执行拦截器链的triggerAfterCompletion
                processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            }
            catch (Exception ex) {
                triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
            }
            catch (Throwable err) {
                triggerAfterCompletion(processedRequest, response, mappedHandler,
                        new NestedServletException("Handler processing failed", err));
            }
            finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    // Instead of postHandle and afterCompletion
                    if (mappedHandler != null) {
                        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                    }
                }
                else {
                    // Clean up any resources used by a multipart request.
                    if (multipartRequestParsed) {
                        cleanupMultipart(processedRequest);
                    }
                }
            }
        }
    

    SpringMVC执行流程:

    1. 用户发送请求至前端控制器DispatcherServlet;
    2. DispatcherServlet收到请求调用处理器映射器HandlerMapping;
    3. 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet;
    4. DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系 列的操作,如:参数封装,数据格式转换,数据验证等操作;
    5. 执行处理器Handler前置拦截器,handler和后置拦截器方法(Controller,也叫页面控制器);
    6. Handler执行完成返回ModelAndView;
    7. HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet;
    8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器;
    9. ViewReslover解析后返回具体View;
    10. DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)然后写入response;
    11. 执行HandlerExecutionChain的triggerAfterCompletion方法;
    12. DispatcherServlet响应用户。

    mybatis 的配置信息都是存储在 mybatis-config.xml 文件中的。作为配置信息肯定只需要读取一次就够了,问题是读取之后 mybatis 是如何维护的呢?很简单,mybatis 框架直接使用了一个 Configuration 对象来维护所有的配置信息。

    Mybatis 初始化过程

    一般我们会这么获取 SqlSession。然后通过 SqlSession 获取我们的 mapper 代理对象,执行接口中的方法。

    Reader re ader = Resources.getResourceAsReader( "mybatis-config.xml");
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
    SqlSession sqlSession = sessionFactory.openSession(true);
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    
    1. 获取输入流;
    2. 获取 SqlSessionFactory;
    3. 获取 SqlSession;
      mybatis 初始化过程就是获取 SqlSessionFactory 的过程


      image.png

      如上图所示,mybatis 初始化主要经过以下几步:

    • 调用 SqlSessionFactoryBuilder 对象的 build(Reader) 方法;
    • SqlSessionFactoryBuilder 根据输入流 Reader 信息创建 XMLConfigBuilder 对象;
    • SqlSessionFactoryBuilder 调用 XMLConfigBuilder 对象的 parse() 方法;
    • XMLConfigBuilder 对象返回 Configuration 对象;
    • SqlSessionFactoryBuilder 根据 Configuration 对象创建一个 DefaultSessionFactory 对象;
    • SqlSessionFactoryBuilder 返回 DefaultSessionFactory 对象给 Client。
    ## SqlSessionFactory.java
    
    public SqlSessionFactory build(Reader reader) {
        return build(reader, null, null);
    }
    
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                reader.close();
            } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
            }
        }
    }
    
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
    

    传播机制类型

    下面的类型都是针对于被调用方法来说的,理解起来要想象成两个 service 方法的调用才可以。

    PROPAGATION_REQUIRED (默认)

    支持当前事务,如果当前没有事务,则新建事务
    如果当前存在事务,则加入当前事务,合并成一个事务

    REQUIRES_NEW

    新建事务,如果当前存在事务,则把当前事务挂起
    这个方法会独立提交事务,不受调用者的事务影响,父级异常,它也是正常提交

    NESTED

    如果当前存在事务,它将会成为父级事务的一个子事务,方法结束后并没有提交,只有等父事务结束才提交
    如果当前没有事务,则新建事务
    如果它异常,父级可以捕获它的异常而不进行回滚,正常提交
    但如果父级异常,它必然回滚,这就是和 REQUIRES_NEW 的区别

    SUPPORTS

    如果当前存在事务,则加入事务
    如果当前不存在事务,则以非事务方式运行,这个和不写没区别

    NOT_SUPPORTED

    以非事务方式运行
    如果当前存在事务,则把当前事务挂起

    MANDATORY

    如果当前存在事务,则运行在当前事务中
    如果当前无事务,则抛出异常,也即父级方法必须有事务

    NEVER

    以非事务方式运行,如果当前存在事务,则抛出异常,即父级方法必须无事务

    Springboot启动流程

    1. 执行SpringApplication的静态run方法进行启动,内部其实也是调用SpringApplication的构造方法创建实例对象;
    2. SpringApplication构造方法内部主要做以下几件事:
      • 初始化自定义ResourceLoader;
      • 通过ClassUtil工具类推断webApplicationType,目前主要有三种应用类型,首先检查DispatcherHandler类是否存在,如果存在并且DispatcherServlet不存在则推断为REACTIVE类型应用,否则继续判断Servlet类和ConfigurableWebApplicationContext是否存在,如果其中一个不存在则推断为普通应用,最后默认为SERVLET类型WEB应用;
      • 通过SpringFactoriesLoader扫描所有jar包classpath下META-INF/spring.factories配置,并加载配置中的所有factory配置到cache中;
      • 根据SpringFactoriesLoader获取指定ApplicationContextInitializer类型配置工厂类并实例化,设置到SpringApplication实例对象中;
      • 根据SpringFactoriesLoader获取指定ApplicationListener类型配置工厂类并实例化,设置到SpringApplication实例对象中;
    3. SpringApplication实例化完成后开始执行run方法,进入启动流程,启动之前首先通过SpringFactoriesLoader加载SpringApplicationRunListeners类型的所有实现类,并将SpringApplication实例对象通过构造方法注入,并遍历执行RunListener实现类的starting方法,通知他们,我要开始启动了;
    4. 准备配置环境,并执行RunListener的environmentPrepared方法,通知他们,配置环境准备好了,并将配置环境绑定到SpringApplication;
    5. 加载spring的默认banner;
    6. 根据webApplicationType类来创建对应的配置应用上下文;
    7. 准备上下文,主要有以下几个步骤:
      • 上下文设置配置环境;
      • 配置上下文的beanNameGenerator和resourceLoader等;
      • 遍历执行ApplicationContextInitializer的所有实现类initialize方法,通知他们应用上下文正在进行初始化操作;
      • 遍历执行SpringApplicationRunListeners的contextPrepared方法,通知他们,上下文正在准备中;
      • 根据上下文修改BeanFactory的特殊配置;进行BeanFactory和启动Banner的自定义配置,主要包括BeanDefinition的覆盖策略;
      • 最后在遍历执行SpringApplicationRunListeners的contextLoaded,通知他们上开始加载上下文;
    8. 上下文准备好之后开始执行刷新上下文操作,并注册关闭钩子函数;
    9. 上下文刷新之后应用上下文容器已经启动完成,开始执行SpringApplicationRunListeners的started方法,通知他们spring容器上下文已经启动完成;
    10. 最后通过spring容器上下文获取所有的ApplicationRunner和CommandLineRunner实现类,并执行他们的run方法。

    Spring容器初始化和getBean回调方法执行流程

    BeanFactoryPostProcessor接口回调-适用于容器初始化阶段

    • 容器初始化阶段执行的BeanDefinitionRegistryPostProcessor回调方法postProcessBeanDefinitionRegistry,该方法允许修改应用上下文中已经按照标准注册好的BeanDefinition对象,所有的标准注册BeanDefinition已经都被加载注册了,但是还没有实例化,在这里允许用户添加自定义的BeanDefinition,该接口继承自BeanFactoryPostProcessor;
    • 容器初始化阶段执行的是BeanFactoryPostProcessor回调方法postProcessBeanFactory,该接口是上一个接口的父接口,类似于方法重载了,两个方法的入参不一样,但是上面方法会优先于本方法执行。

    bean的Aware接口回调

    • BeanNameAware
    • BeanClassLoaderAware
    • BeanFactoryAware

    BeanPostProcessor接口回调-适用于bean实例化阶段

    • postProcessBeforeInitialization方法,适用于bean实例化完成之前执行,可以通过该方法修改实例化完成之前的bean对象;
    • postProcessAfterInitialization方法,适用于bean实例化完成和执行了各种初始化接口之后执行,通过该方法可以直接获取实例化完成的bean对象。

    ApplicationContextAware接口回调-适用于bean实例化完成后阶段

    • 该感知接口是通过ApplicationContextAwareProcessor实现,该实现类是基于BeanPostProcessor的postProcessBeforeInitialization方法触发执行的;
    • 类似的感知上下文环境接口还有以下几个,按照执行顺序排序如下:
      1. EnvironmentAware
      2. EmbeddedValueResolverAware
      3. ResourceLoaderAware
      4. ApplicationEventPublisherAware
      5. MessageSourceAware
      6. ApplicationContextAware

    执行InitializingBean接口的afterPropertiesSet方法

    执行init-method属性指定的方法

    bean销毁阶段

    • 执行DisposableBean接口的destroy方法
    • 执行destory-method属性指定的方法

    分布式事务解决方案

    分布式事务的主要解决方案

    • XA方案 - 两阶段提交方案
    • TCC方案
    • 本地消息表
    • 可靠信息最终一致方案
    • 最大努力通知方案

    两阶段提交方案

    所谓的 XA 方案,即:两阶段提交,有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。
    这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。如果要玩儿,那么基于 Spring+JTA 就可以搞定,自己随便搜个 demo 看看就知道了。
    这个方案,我们很少用,一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几十个甚至几百个服务。一般来说,我们的规定和规范,是要求每个服务只能操作自己对应的一个数据库。
    如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。
    如果你要操作别人的服务的库,你必须是通过调用别的服务的接口来实现,绝对不允许交叉访问别人的数据库。

    TCC方案

    TCC 的全称是:Try、Confirm、Cancel。

    • Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。
    • Confirm 阶段:这个阶段说的是在各个服务中执行实际的操作。
    • Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作(把那些执行成功的回滚)
      因为这个事务回滚实际上是严重依赖于你自己写代码来回滚和补偿了,会造成补偿代码巨大,非常之恶心。比如说我们,一般来说跟钱相关的,跟钱打交道的,支付、交易相关的场景,我们会用 TCC,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题。但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码很难维护。

    本地消息表

    • A系统在自己本地一个事务里操作同时,插入一条数据到消息表;
    • 接着A系统将这个消息发送到 MQ 中去;
    • B系统接收到消息之后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样保证不会重复处理消息;
    • B系统执行成功之后,就会更新自己本地消息表的状态以及 A 系统消息表的状态;
    • 如果B系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理;
    • 这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止。
      这个方案说实话最大的问题就在于严重依赖于数据库的消息表来管理事务啥的,会导致如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。

    可靠消息最终一致性方案

    不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。
    大概的流程就是:

    • A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;
    • 如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息;
    • 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;
      mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。
    • 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。
    • 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。

    最大努力通知方案

    • 系统 A 本地事务执行完之后,发送个消息到 MQ;
    • 这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口;
    • 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。

    设计模式

    设计模式-原则

    1. 开闭原则:一个软件实体应当对扩展开放,对修改关闭,尽量在不修改原有代码的情况下进行扩展。
    2. 里式替换原则:所有引用基类对象的地方能够透明的使用其子类的对象。
    3. 依赖倒置原则:抽象不应该依赖具体类。要针对接口编程,而不是针对实现编程。
    4. 单一职责原则:一个类只负责一个功能领域中的相应职责。
    5. 迪米特法则:最少知道原则,一个软件实体应当尽可能少地与其他实体发生相互作用。
    6. 接口分离原则:使用多个专门的接口,而不使用单一的总接口,客户端不应该依赖那些不需要的接口。
    7. 合成复用原则:尽量首先使用合成/聚合的方式,而不是使用继承。

    23种设计模式

    创建型模式

    1. 工厂方法模式 mybatis的DefaultSqlSessionFactory,MapperProxyFactory
    2. 抽象工厂模式 spring的FactoryBean就是一个工厂抽象接口
    3. 单例模式 jackson工具类
    4. 建造器模式 mybatis的SqlSessionFactoryBuilder
    5. 原型模式

    结构型模式

    1. 外观模式 - SLF4J
    2. 适配器模式 - RPC接口封装
    3. 桥接模式 - jcl-over-slf4j
    4. 代理模式 - aop
    5. 装饰者模式 - FileInputStream ByteArrayInputStream
    6. 享元模式 - 连接池 字符串常量池
    7. 组合模式 - ReterentLock使用AQS同步器

    行为型模式

    1. 职责链模式 - filter netty-handler
    2. 命令模式 ssiFilter
    3. 解释器模式
    4. 迭代器模式
    5. 中介者模式
    6. 备忘录模式
    7. 观察者模式 spring Listener
    8. 状态模式
    9. 策略模式 不同任务类型调用不同任务处理器
    10. 模板方法模式 AQS提供的模板方法
    11. 访问者模式

    DDD领域驱动设计

    领域模型设计

    传统方式则由底层DB模型到顶层服务接口开始设计;
    领域驱动设计由顶层领域模型置底开始设计。
    领域 -> 多级业务子域 -> 领域服务 -> 领域能力 -> 领域能力扩展点
    系统 -> 多个子应用 -> 服务接口 -> 服务方法 -> 对外暴露扩展接口
    示例:
    商品系统 -> 商品详情页 -> 商品详情服务接口 -> 查询商品基本信息 -> 根据扩展接口实现自定义查询字段
    领域能力中的实现是中台提供的水平业务能力组件,可以供各个业务方直接使用。
    领域能力扩展点由垂直业务方实现,当一个能力扩展点被多个垂直业务方都使用时,可以沉淀为水平业务能力,设置为默认实现,减少垂直业务方的配置。
    这样可以通过复用中台的水平业务能力组件实现业务的快速开发,通过领域能力扩展点实现各业务方的差异化能力。
    每个叶子域下会有多个领域服务,所谓领域服务,就是指各个领域能对外提供的原子性的业务逻辑,比如库存查询服务,扣减库存服务。领域服务和JSF接口有明确的区别,领域服务属于领域建模的概念,是描述中台内部业务逻辑的手段;JSF接口是技术实现方式,会对领域服务做编排、聚合后暴露给业务方进行调用。
    领域服务负责实现业务逻辑,并编排领域能力,一个领域服务由多个领域能力支撑,一个领域能力能支持多个领域服务。
    领域能力是PaaS化业务建模体系中描述核心逻辑最细粒度的概念,一个领域能力可以映射到一组相关扩展点的集合。中台在定义扩展点时会将一个业务场景需要同时定制的扩展点聚合在一起,前台业务可以根据领域能力来索引这些扩展点。领域能力必须有扩展点,否则会出现随便定义的情况。
    领域能力的一些业务策略,不同业务有不同的需求,这种因业务而异、无法确定的逻辑被定义成扩展点,由前台业务自行设置。
    比如订单取消服务(领域服务),由订单状态判断、订单有效期判断等多个领域能力支撑,其中订单有效期面对秒杀、预售、手机充值等不同业务场景会有不同的有效时间,这个有效时间可以做成一个扩展点:订单有效期,由前台业务进行定义。
    扩展点有版本概念,业务包一般基于一个版本开发,所以扩展点的升级需要做到多版本兼容,降低前台研发使用成本。扩展点就是SPI,只是传统的SPI没有业务身份的概念。

    相关文章

      网友评论

          本文标题:框架相关

          本文链接:https://www.haomeiwen.com/subject/zrzvnhtx.html