什么是IOC?怎么实现?

  • IOC(Inversion of Control)控制反转,是一种设计思想,把创建对象的控制权交给IOC容器来管理,并由IOC容器来完成对象的注入。
  • 被IOC容器管理的对象叫做Bean。
  • 简化开发,实现解耦。
  • IOC 和 DI 依赖注入其实是同一个东西。
  • IOC容器如同一个工厂一样,只需配置好相应的配置文件/注解即可,无需考虑对象是如何创建的。
  • 实现原理:工厂模式+反射

什么是AOP?作用是啥?

  • AOP(Aspect-Oriented Programming,面向切面编程),能将一些与业务关系不大,但又被各个业务模块共同调用的通用功能(日志、鉴权、事务管理等)封装起来,在不改变原有代码的基础上进行功能增强,减少重复代码,降低耦合,便于后期维护拓展。

  • Spring AOP 基于动态代理实现,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。

  • 也可以使用AspectJ实现AOP。

AOP有哪些核心概念?

  • 目标(Target):要实现AOP增强的具体的目标对象。
  • 代理(Proxy):对目标对象实现AOP时创建的代理对象。
  • 连接点(JoinPoint):目标对象中的定义的所有方法
  • 切入点(Pointcut):目标对象中需要被增强的方法(连接点包含切入点)。
  • 通知/通知类(Advice):增强的功能代码,抽象到一个方法中,方法不能独立存在,所以就放到一个类中,叫通知类。
  • 切面(Aspect):通知与切入点之间的对应关系
  • 织入(Weaving):将通知应用到目标对象时创建代理对象的过程

AOP有哪些应用场景?

  • 日志
  • 事务管理(调用方法前开启事务,结束后提交事务)
  • 鉴权
  • 参数校验
  • 性能监控(统计方法执行时间)
  • 缓存优化(第一次查数据库,将查询结果放入缓存, 第二次查询,直接返回缓存中的对象,不需要查询数据库)

Spring容器的启动和关闭流程

简单版:

  1. 加载配置文件:Spring容器从配置文件中读取Bean的定义信息,并将其存储在内存中。
  2. 创建和注册Bean:根据配置文件中的Bean定义信息,Spring容器创建对应的Bean实例。
  3. 依赖注入:Spring容器根据Bean之间的依赖关系,将依赖的Bean实例注人到目标Bean中。
  4. 初始化Bean实例: 对Bean进行初始化操作,如调用初始化方法、执行自定义初始化逻辑等。
  5. 启动应用上下文:完成以上步骤后,Spring容器启动应用上下文,等待其他组件的请求。
  6. 销毁Bean: 当应用上下文关闭时,Spring容器会销毁所有Bean实例,释放资源。
  7. 销毁容器

启动

虽然ApplicationContext继承自BeanFactory,更确切地说是ApplicationContext内部持有了一个实例化的BeanFactory(DefaultListableBeanFactory)BeanFactory的相关操作其实是委托给这个实例来处理。

xml或者注解的形式启动容器

  • xml:
    1. 调用父类的构造函数:如果有父容器就注册一下,以及创建一个处理xml的命名Patten的resourcepatternresolver。
    2. 用resourcepatternresolver处理xml文件名:resolver里有placeholderresolver支持文件名有${xxx}等的placeholder占位符。
    3. 调用refresh():是核心函数。
public ClassPathXmlApplicationContext(
    String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
    throws BeansException {
    //如果已经有application Context ,并需要配置成父子关系, 调用该构造方法
    super(parent);
    // 根据提供的路径,处理成配置文件数组(以分号、逗号、空格、tab、换行符分割)
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();//核心!!!
    }

}
  • 注解:
    1. 调用本类的构造函数:创建beandefinition的reader和scanner。
    2. scan函数将被注解的类的相关配置转换成了beandefinition。
    3. refresh
public AnnotationConfigApplicationContext(String... basePackages) {
    this();
    scan(basePackages);//这个过程中配置已经被转化为一个个的beanDefinition
    refresh();
}

refresh流程:

@Override
public void refresh() throws BeansException, IllegalStateException {
    //保证容器启动销毁操作的并发安全
    synchronized (this.startupShutdownMonitor) {
        //准备工作, 记录容器的启动时间, 标记已启动状态, 处理配置文件种的占位符
        prepareRefresh();

        //这步用于将配置文件解析成一个个bean definition,注册到重建的beanFactory中,(只是提取了配置信息,bean并没有初始化),同时还设置两个配置属性:1、是否允许bean覆盖2、是否允许循环引用
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        //准备bean工厂 :设置beanFactory的类加载器, 添加几个beanPostProcessor ,手动注册几个特殊的bean
        prepareBeanFactory(beanFactory);
        try {
            
            //实现bean工厂的后处理 :如果beanfactory实现了beanFactoryPostProcessor 将会执行postProcessBeanFactory方法提供子类的扩展点,到这bean都加载、注册完成,但没有初始化,具体的子类可以在这步添加特殊beanFactoryPostProcessor实现类做事
            postProcessBeanFactory(beanFactory);

            //调用bean工厂的后处理 :调用beanFactoryPostProcessor的各个实现类的postProcessBeanFactory方法
            invokeBeanFactoryPostProcessors(beanFactory);

            //注册bean的后处理 :注册BeanPostProcessor的实现类,BeanPostProcessor将在bean初始化前后执行
            registerBeanPostProcessors(beanFactory);

            //初始化当前 ApplicationContext 的 MessageSource,国际化
            initMessageSource();

            //初始化当前 ApplicationContext 的事件广播器
            initApplicationEventMulticaster();

            //模板方法(钩子方法,具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
            onRefresh();

            //注册事件监听器,监听器需要实现 ApplicationListener 接口。
            registerListeners();

            //实例化+初始化所有的非懒加载的单例bean
            finishBeanFactoryInitialization(beanFactory);

            //广播事件,ApplicationContext 初始化完成
            finishRefresh();
        }

        catch (BeansException ex) {
            // Destroy already created singletons to avoid dangling resources.销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
            destroyBeans();
            cancelRefresh(ex);
            // Propagate exception to caller.
            throw ex;
        }
        finally {
            resetCommonCaches();
        }
    }
}

img

关闭

protected void doClose() {
    if (this.active.get() && this.closed.compareAndSet(false, true)) {
        if (logger.isDebugEnabled()) {
            logger.debug("Closing " + this);
        }
		//取消注册上下文
        if (!IN_NATIVE_IMAGE) {
            LiveBeansView.unregisterApplicationContext(this);
        }
        // 发布事件
        publishEvent(new ContextClosedEvent(this));

        // 停止所有Lifecycle bean,以避免在销毁期间造成延迟。
        if (this.lifecycleProcessor != null) {
            this.lifecycleProcessor.onClose();
        }

        // 销毁上下文的BeanFactory中所有缓存的单例。
        destroyBeans();

        // 关闭此上下文本身的状态。
        closeBeanFactory();

        // 让子类善后
        onClose();

        // 充值本地应用坚硬其为pre-refresh状态
        if (this.earlyApplicationListeners != null) {
            this.applicationListeners.clear();
            this.applicationListeners.addAll(this.earlyApplicationListeners);
        }

        // 切换为非活动状态
        this.active.set(false);
    }
}

大概就是:先发布事件,再摧毁Factory中的bean,再摧毁Factory本身,最后设置一些状态。

而Bean周期中的销毁部分就存在于destroyBeans中。当然,销毁bean也是需要先销毁它所依赖的bean。

Spring AOP 和 AspectJ AOP 有什么区别?

  • Spring AOP 是运行时增强,基于动态代理,Spring AOP的支持的动态代理又有两种实现:

    • JDK动态代理:(默认这个,没有实现接口的话就会用CGLIb)

      • jdk自带,无需加载第三方类,使用简单。

      • 通过拦截器+反射实现。

      • **目标类必须实现自己业务的某个接口(面向接口做代理,不能对具体实现类做代理)。**比如:

        public interface IService {
            void serviceMethod();
        }
        
        public class MyTarget implements IService {
            @Override
            public void serviceMethod() {
                //.....
            }
        }
        
      • 动态代理类需要实现InvocationHandler接口,通过Proxy类的newProxyInstance()方法获取代理对象,然后重写invoke()方法在其中使用反射执行目标类的方法,并在其前后做增强(重写的invoke()用来拦截需要代理的方法)。

        public interface InvocationHandler { // jdk中原生的接口
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; 
        }
        
        // jdk动态代理类,T就是目标类
        public class MyJdkProxy<T> implements InvocationHandler {
        
            // 被代理的目标对象,实际的方法执行者
            private T target;
        
            public MyJdkProxy(T target) {
                this.target = target;
            }
        
            // 此方法为自己定义的方法,用来获取目标对象的动态代理对象
            public static <T> T getProxy(T t) { // t就是目标对象
                Object o = Proxy.newProxyInstance(t.getClass().getClassLoader()
                        , t.getClass().getInterfaces() // 所有被代理对象需要实现同一个接口,也就是IService
                        , new MyJdkProxy(t));
                return (T) o;
            }
            
            // 拦截需要代理的方法
            @Override
            public Object invoke(Object proxy,
                                 Method method,
                                 Object[] args) throws Throwable {
                
                // 调用invoke方法之前执行,属于增强代码
                System.out.println("before增强");
                
                // 被代理对象要执行的方法,通过反射来执行
                Object invoke = method.invoke(target, args); 
                
                // 调用invoke方法之后执行,属于增强代码
                System.out.println("返回值:" + invoke);
                
                return null;
            }
        }
        

        最终使用方式:

        IService myTarget = new MyTarget();
        IService myTargetJdkProxy = MyJdkProxy.getProxy(myTarget);
        myTargetJdkProxy.serviceMethod(); // 执行这个方法就会执行增强方法
        
    • CGLIB:

      • 是第三方工具类,基于ASM实现,不需要反射。

      • 无需通过接口来实现,它直接针对实现类进行代理,原理是对目标类继承(所以不能代理final修饰的类)一个子类(Enhancer类(是字节码增强器)),其实就是new一个enhancer对象,通过对象将目标类设置为该Enhancer的父类,在子类中,实现 MethodInterceptor接口,重写intercept(),拦截所有的父类方法的调用,通过enhancer的create()方法获取代理对象

        public class CgLibProxy {
            // 自定义方法来获取代理类
            public static <T> T createProxy(T t) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(t.getClass()); // new一个enhancer对象,并将目标类设置为Enhancer的父类
                
                // 实现 MethodInterceptor接口,拦截所有的父类方法的调用
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj,
                                            Method method,
                                            Object[] args,
                                            MethodProxy methodProxy) throws Throwable {
           
                        System.out.println("before增强");
                        // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法。
                        Object invoke = methodProxy.invokeSuper(obj, args); // 这不是反射,而是靠ASM来实现
                        return invoke;
                    }
                });
                Object o = enhancer.create(); // 创建/获取代理对象
                return (T) o;
            }
        }
        

        最终使用方式:

        public class MyTarget {
            public void serviceMethod() {
                //.....
            }
        }
        
        MyTarget mt = new MyTarget();
        MyTarget mtProxy = CgLibProxy.createProxy(mt);
        mtProxy.serviceMethod(); // 执行这个方法就会执行增强方法
        

      image-20221014110233925

  • AspectJ AOP 是编译时增强,在编译时的字节码层面就自动帮我们实现了增强(也就是基于静态代理),在切面比较多时,会比spring AOP快很多。

AspectJ AOP Advice 通知类型有哪些?

  • 前置通知:@Before,通知在切入点之前执行。
  • 后置通知:@After
  • 返回后通知:@AfterReturning,在切入点正常执行完后执行通知。
  • 环绕通知:@Around,通知在切入点前后都执行。
  • 抛出异常后通知::@AfterThrowing,切入点抛出异常后执行通知。

什么是Bean?

bean就是被IOC容器管理的对象。通过配置类/配置文件/注解来定义bean。

定义Bean有哪些注解?

  • 定义在上:(让Spring去扫描找哪里有bean)
    • @Repository :dao层用
    • @Service:service层用
    • @Controller:controller层用
    • @Component:通用的注解,如果不知道bean属于哪层就用这个。
  • 定义在方法上:
    • @Bean(告诉spring该方法返回的对象注册成bean)自定义能力更强,而且很多地方只能通过@Bean注解来注册bean,如在使用第三方库的配置类需要装配到IOC容器里时只能用@Bean。一般需要配合@Configuration来告诉该类为配置类,或@ComponentScan,让spring来扫描

有多少种方式实现bean属性的依赖注入?

4种:

  • 注解注入(@Autowired 和 @Resource)
  • 构造函数注入
  • setter注入
  • 接口注入(不推荐,侵入性强)

@Autowired 和 @Resource 的区别是什么?

提供 默认注入方式 指定名称 指定类型
@Autowired spring byType 按类型 @Qualifier(value = “xxx”) /
@Resource jdk byName 按类名 @Resource(name=“xxx”) @Resource(type=“xxx”)

Bean 的作用域(scope)有哪些?

  • singleton(默认):单例,全部请求只用一个实例
  • prototype:多例,每次请求都创建一个实例。
  • 下面的仅web应用可用:
    • request:每次http请求都创建一个新的bean,仅在当前request中有效
    • session:每次来自新session的http请求都创建一个新的bean,仅在当前session中有效
    • application/global-session:每个web应用启动时创建一个新的bean,仅在当前应用启动时有效
    • websocket:每次websocket会话创建一个新的bean

单例的bean线程安全吗?

线程不安全。

大部分bean都是无状态的,可认为是线程安全的。

解决方案:

  1. bean中定义Threadlocal变量,将可变的成员变量放在Threadlocal中。(推荐)
  2. bean中尽量避免定义可变的成员变量。(不太现实)

bean的生命周期?

Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类

  • Bean自身的方法: 这个包括了Bean本身调用的方法和通过配置文件中<bean>的init-method和destroy-method指定的方法
  • Bean级生命周期接口方法: 这个包括了BeanNameAware、BeanFactoryAware、ApplicationContextAware;当然也包括InitializingBean和DiposableBean这些接口的方法(可以被@PostConstruct和@PreDestroy注解替代)
  • 容器级生命周期接口方法: 这个包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
  • 工厂后处理器接口方法: 这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。

img

具体流程:

  • 如果 BeanFactoryPostProcessor 和 Bean 关联, 则调用postProcessBeanFactory方法.(即首先尝试从Bean工厂中获取Bean)

  • 如果 InstantiationAwareBeanPostProcessor 和 Bean 关联,则调用postProcessBeforeInstantiation方法

  • 根据配置情况调用 Bean 构造方法初始化 Bean

  • 利用依赖注入(如setter方法)完成 Bean 中所有属性值的配置注入

  • 如果 InstantiationAwareBeanPostProcessor 和 Bean 关联,则调用postProcessAfterInstantiation方法和postProcessProperties

  • 调用xxxAware接口 (上图只是给了几个例子)

    • 第一类Aware接口

      • 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
      • 如果 Bean 实现了 BeanClassLoaderAware 接口,则 Spring 调用 setBeanClassLoader() 方法传入classLoader的引用。
      • 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
    • 第二类Aware接口

      • 如果 Bean 实现了 EnvironmentAware 接口,则 Spring 调用 setEnvironment() 方法传入当前 Environment 实例的引用。
      • 如果 Bean 实现了 EmbeddedValueResolverAware 接口,则 Spring 调用 setEmbeddedValueResolver() 方法传入当前 StringValueResolver 实例的引用。
      • 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  • 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。

  • 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。(或者有执行@PostConstruct注解的方法)

  • 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。

  • 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。

  • 如果在 <bean> 中指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 <bean> 中指定了该 Bean 的作用范围为 scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。

  • 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destroy() 方法将 Spring 中的 Bean 销毁;(或者执行@PreDestroy注解的方法)

  • 如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

总体下来的大致流程是:

  1. 尝试从bean工厂中获取bean
  2. 根据配置情况调用bean的构造方法初始化bean
  3. 利用依赖注入(如setter方法)给bean注入属性值
  4. 调用各种XXXAware接口
  5. 如果配置了 init-method ,就执行该方法
  6. 使用
  7. 调用destroy方法销毁bean
  8. 如果配置了destory-method,就执行该方法

简单讲讲对SpringMVC的理解

  • M(模型) :数据,业务处理

  • V(视图):数据显示

  • C(控制器):接收请求,调用业务,返回数据给前端

image-20221014160032816

MVC是一种设计模式,核心思想是把业务逻辑、数据、显示分开,实现解耦。

spring MVC 是当下最优秀的MVC框架,简化web开发,将web后端项目分成:

  • Service层:处理业务
  • Dao层:数据持久化
  • Entity层:实体类(承载数据的类)
  • Controller层:接口调用,返回数据给前端

spring MVC的核心组件有哪些?

  • DispatcherServlet核心的中央处理器,负责接收请求分发请求返回响应
  • HandlerMapping处理器映射器,根据url找到能处理此请求的处理器Handler,并将请求涉及的拦截器和Handler封装在一起返回。
  • HandlerAdapter处理器适配器,根据找到的Handler去适配执行Handler
  • Handler请求处理器,处理实际请求(也就是平常说的controller),返回数据对象(model)和逻辑视图
  • viewResolver视图解析器,将逻辑视图解析渲染成真正的视图(view)

Servlet是什么?

servlet就是一个Java接口,是JavaEE规范的一种,主要是为了扩展Java作为Web服务的功能。由其他内部厂商如tomcat,JBoss内部实现web的功能。

一个http请求到来后,容器将请求封装为servlet中的HttpServletRequest对象,调用init(),service()等方法输出response,由容器包装为httpresponse返回给客户端。

spring mvc和servlet有什么关系?

Springmvc的核心是一个DispatcherServlet(DispatcherServlet继承自 FrameworkServlet继承自HttpServletBean 继承自HttpServlet,也就是servelt)和servlet 本质上是一样的东西。

spring基于tomcat等这些servlet容器对我们的请求能做更多的事情,如校验,拦截(AOP思想),后期渲染等等,好让我们专注于业务的开发。

servlet、filter和listener是javaweb的三大组成部分,其中servlet通常是由web.xml统一管理。

spring可以管理servlet和springmvc中的javabean,这个意思就是说servlet和springmvc是相对独立的两个servlet,

当你想要在一个继承servlet的实现类中,控制url映射的时候,必须在web.xml配置对应关系,即使你把这个servlet交给spring去管理。

spring MVC 工作流程是什么?

image-20221014162138990

  1. DispatcherServlet接收用户的请求
  2. DispatcherServlet调用HandlerMapping,根据url找到对应的**Handler,并连同请求涉及的拦截器**封装在一起返回。
  3. DispatcherServlet调用HandlerAdapter,根据找到的Handler去适配执行Handler。(上一步只是找,这一步就执行)
  4. Handler执行完后返回数据对象(model)和逻辑视图(ModelAndView)给DispatcherServlet
  5. viewResolver将逻辑视图解析渲染成真正的视图(view),并返回给DispatcherServlet
  6. DispatcherServlet将model传给view。
  7. DispatcherServlet返回view给用户。

全局异常处理器怎么做?

@RestControllerAdvice/@ControllerAdvice + @ExceptionHandler 给每个controller方法都织入异常处理(AOP),当有controller抛异常后,会被异常对应的@ExceptionHandler修饰的方法处理。

@RestControllerAdvice/@ControllerAdvice的区别是前者返回json数据,后者需要@ResponseBody才能返回json。和@RestController/@Controller的区别是一样的。

@RestControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    @ExceptionHandler(BaseException.class)
    public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
      //......
    }

    @ExceptionHandler(value = ResourceNotFoundException.class)
    public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
      //......
    }
}

ExceptionHandlerMethodResolvergetMappedMethod 方法决定了异常具体被哪个被 @ExceptionHandler 注解修饰的方法处理异常。

@Nullable
	private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
		List<Class<? extends Throwable>> matches = new ArrayList<>();
    //找到可以处理的所有异常信息。mappedMethods 中存放了异常和处理异常的方法的对应关系
		for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
			if (mappedException.isAssignableFrom(exceptionType)) {
				matches.add(mappedException);
			}
		}
    // 不为空说明有方法处理异常
		if (!matches.isEmpty()) {
      // 按照匹配程度从小到大排序
			matches.sort(new ExceptionDepthComparator(exceptionType));
      // 返回处理异常的方法
			return this.mappedMethods.get(matches.get(0));
		}
		else {
			return null;
		}
	}

从源代码看出: getMappedMethod()会首先找到可以匹配处理异常的所有方法信息,然后对其进行从小到大的排序,最后取最小的那一个匹配的方法(即匹配度最高的那个)来处理该异常。

spring中主要用到了哪些设计模式?

  • 工厂模式:spring使用BeanFactory和ApplicationContext创建bean。
  • 单例模式:默认是单例的bean。
  • 代理模式:spring AOP的实现。
  • 模板方法模式:spring的JdbcTemplate、HibernateTemplate等XXXTemplate对数据库操作的模板类。
  • 观察者模式:spring的事件驱动(io多路复用)模型。
  • 适配器模式:Spring MVC中的HandlerAdapter、spring AOP的Advice。
  • 包装器模式:根据客户的需求动态切换不同的数据源。

spring实现事务管理的方式有哪些?

  • 编程式事务 : 在代码中硬编码(不推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
  • 声明式事务 : 在 XML 配置文件中配置或者直接基于注解(推荐使用),实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

spring事务角色?

  • 事务管理员:开启事务的一方(@Transaction)
  • 事务协调员:被调用方

image-20221014203943014

spring事务传播行为有哪些?

spring事务传播行为是为了解决业务层之间方法的互相调用问题。例如,当一个事务方法被另一个事务方法所调用,可以指定被调用事务方法加入到调用事务中运行,也可以开启一个新事务在自己的事务中运行。可在@Transactional(propagation = xxx)中用TransactionDefinition接口定义的常量设置。

image-20221014203903869

一共有7种:

  • 加入事务开启方(管理员):

    • PROPAGATION_REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务

    • PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行

    • PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。

  • 不加入事务开启方(管理员):

    • PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。不管外部有没有事务,都新开自己的事务,且与外部事务独立,互不干扰。

    • PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。不管外部有没有事务,都不开事务,也不加入。

    • PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常

  • 其他情况:

​ PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。

image-20221014203630926

spring 事务的隔离级别有哪些?

可在@Transactional(isolation = xxx)中用TransactionDefinition接口定义的常量设置,一共有5种:基本跟mysql那些一样

  • ISOLATION_DEFAULT:使用的数据库默认的隔离级别

  • ISOLATION_READ_UNCOMMITTED:读未提交

  • ISOLATION_READ_COMMITTED:读已提交

  • ISOLATION_REPEATABLE_READ:可重复读

  • ISOLATION_SERIALIZABLE:串行化

@Transactional(rollbackfor = Exception.class)了解吗?

异常有运行时异常和非运行时异常,@Transactional默认是只在出现运行时异常回滚。Exception.class能在出现非运行时异常也能回滚。

什么是SpringBoot?

Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

  • 用来简化Spring应用的初始搭建以及开发过程,使用特定的方式来进行配置
  • 创建独立的Spring引用程序main方法运行
  • 嵌入的tomcat无需部署war文件
  • 简化maven配置
  • 自动配置Spring添加对应的功能starter自动化配置
  • SpringBoot来简化Spring应用开发,约定大于配置,去繁化简

为什么使用SpringBoot?

  • 独立运行

Spring Boot 而且内嵌了各种 servlet 容器,Tomcat、Jetty 等,现在不再需要打成war 包部署到容器中,Spring Boot 只要打成一个可执行的 jar 包就能独立运行,所有的依赖包都在一个 jar 包内。

  • 简化配置

spring-boot-starter-web 启动器自动依赖其他组件,简少了 maven 的配置。

  • 自动配置

Spring Boot 能根据当前类路径下的类、jar 包来自动配置 bean,如添加一个 spring

boot-starter-web 启动器就能拥有 web 的功能,无需其他配置。

  • 无代码生成和XML配置

Spring Boot 配置过程中无代码生成,也无需 XML 配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是 Spring4.x 的核心功能之一。

  • 应用监控

Spring Boot 提供一系列端点可以监控服务及应用,做健康检测。

springboot自动装配的原理

在启动类上添加了@SpringBootApplication或@EnableAutoConfiguration,会自动去maven中读取每个starter中的spring.factory文件,该文件里配置了所有需要被创建的Spring容器中的bean。

springboot的核心配置注解是什么?它主要由哪些注解组成?

核心:@SpringBootApplication

组成:

  • @EnableAutoConfiguration:打开自动配置
  • @ComponentScan:component扫描,默认扫描@ComponentScan所在类所在包下的所有类。
  • @Configuration:额外的配置类bean

SpringBoot的核心配置文件有哪几个?他们的区别是什么?

两个:application.xxx 和 bootstrap.xxx

  • application:用于springboot项目的配置

  • bootstrap:在spring cloud项目中给某个服务配置

    • 特点:

      • 最先加载,比application先加载
      • 里面的属性不能被覆盖,所以可以用来配置最基础最根本的配置
    • 使用场景:

      • 微服务项目中配置配置中心
      • 加密解密的场景
      • 配置最基础最根本的配置

springboot配置文件优先级

  • file(根目录下):config/application
  • file:application
  • classpath(类路径,也就是resource下):config/application
  • classpath:application

SpringBoot 打的jar包和普通的jar包有什么区别?

  • 普通的jar包可以被引用

  • SpringBoot 打的jar包是可执行jar包,只能被执行不能被引用

    主要还是他和普通 jar 的结构不同。普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。

请求参数相关注解有哪些?区别是啥?

  • @PathVariable:获取路径参数/abc/{id}id值

  • @RequestParam:获取查询参数/abc/123?name=efg问号后面跟的就是查询参数name,可获得数据name=efg。可以加上@RequestParam(require = false),表示不一定得有这个参数,没有这个参数不会抛异常。

  • @RequestBody:获取请求体部分且context-type为application/json格式的数据,会将json字符串转换成java对象。

#{}和${}的区别是什么?

  • ${}:比较通用,很多地方都可以用,可用于properties文件中,也可以用于mybatis的sql中。仅仅是静态字符串替换,用于sql上时会造成sql注入,不建议用。
  • #{}:用于mybatis的sql中。结合预编译处理,mybatis会将#{}换成?,使用PreparedStatement的set方法来赋参数值,能防止sql注入

mybatis的mapper 的 xml 文件中都有哪些常见标签?

  • 顶级标签:
    • <select> :查询
    • <insert> :插入
    • <delete> :删除
    • <update> :更新
    • <resultMap> :比较重要,用来定义mysql的字段和java对象的属性的映射关系。
    • <sql> :定义可被其他sql语句引用的可重用sql语句块
    • <include> :引用被<sql> 定义好的sql
    • <selectKey> :不支持自增的主键生成策略标签
    • <cache> :给该命名空间配置缓存
    • <cache-ref> :引用另一个命名空间的缓存配置。
  • 动态sql相关标签:
    • <if>
    • <choose>(<when> 、<otherwise>) :类似switch
    • <trim>(<where>、<set>)
    • <foreach> :用于对集合的操作,如遍历,判断值是否在集合中等
    • <bind>

简述 MyBatis 的 xml 映射文件和 MyBatis 内部数据结构之间的映射关系?

  • 将所有xml配置信息都封装到一个Configuration对象中。
  • <resultMap>会被解析为ResultMap对象,其中的每个子标签被解析为ResultMapping对象。
  • 每个<select>、<insert>、<update>、<delete>对象都被解析为MappedStatement对象。标签里面的sql语句被解析为BoundSql对象。

Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?

  • 工作原理:jdk的动态代理,为dao接口生成proxy对象,proxy对象拦截接口方法,转而执行xml文件中的sql,执行完后返回结果。

  • dao接口方法可以重载,但这些方法对应xml标签语句只能有一个(id不能重复)。

    /**
     * Mapper接口里面方法重载
     */
    public interface StuMapper {
        Person queryById();
    
        Person queryById(@Param("id") Long id);
    
        Person queryById(@Param("id") Long id, @Param("name") String name);
    }
    
    <select id="queryById" resultMap="PersonMap">
        select
          id, name, age, address
        from person
        <where>
            <if test="id != null">
                id = #{id}
            </if>
            <if test="name != null and name != ''">
                name = #{name}
            </if>
        </where>
        limit 1
    </select>
    

MyBatis 是如何进行分页的?分页插件的原理是什么?

  • 有多种方式可以实现分页:

    • mybatis提供了RowBounds分页类进行分页,对ResultSet进行内存分页物理分页就是调用mysql的limit,内存分页就是没有调用的数据库的关键字,只是全部查出来用程序分页),不是物理分页
    • 也可以在sql语句中自己写物理分页。
    • 使用分页插件(最广泛的是Pagehelper插件)实现物理分页。
  • 分页插件原理:使用mybatis的插件接口,自定义插件,使用拦截器拦截需要执行的sql,然后重写该sql加上物理分页语句和物理分页参数。

简述 MyBatis 的插件运行原理,以及如何编写一个插件。

插件运行原理:使用jdk动态代理,为需要拦截的接口生成代理对象,用拦截器拦截接口方法

mybatis只能自定义针对 ParameterHandlerResultSetHandlerStatementHandlerExecutor 这 4 种接口的插件。

自定义插件流程:

  1. 创建一个类,实现拦截器Interceptor接口
  2. 在该类上@Intercepts注解完成插件签名,指定对哪个对象的哪个方法进行拦截。
  3. 将写好的插件注册到全局配置文件中。

MyBatis 执行批量插入,能返回数据库主键列表(比如插入的那些id)吗?

能。jdbc能,mybatis也能。

MyBatis 的 xml 映射文件中,不同的 xml 映射文件,id 是否可以重复?

如果配置了namespace命名空间,id就可以重复,否则不可以。

底层用的是Map<String, MappedStatement>,namespace+id作为key。

MyBatis 中如何执行批处理?

BatchExecutor。

MyBatis 都有哪些 Executor 执行器?它们之间的区别是什么?

  • SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象(jdbc中的),用完立刻关闭 Statement 对象。

  • ReuseExecutor:和连接池差不多,重复使用。每次执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement>内,供下一次使用。

  • BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同。

Executor的这些特点,都严格限制在 SqlSession 生命周期范围内。

MyBatis 中如何指定使用哪一种 Executor 执行器?

有两种方法:

  1. 配置文件中指定:
mybatis:
  executor-type: SIMPLE
  1. 手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数。

MyBatis 映射文件中,如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面?

定义在哪里都可以正确解析。

原理是,MyBatis 解析 A 标签,发现 A 标签引用了 B 标签,但是 B 标签尚未解析到,尚不存在,此时,MyBatis 会将 A 标签标记为未解析状态,然后继续解析余下的标签,包含 B 标签,待所有标签解析完毕,MyBatis 会重新解析那些被标记为未解析的标签,此时再解析 A 标签时,B 标签已经存在,A 标签也就可以正常解析完成了。

上一篇 下一篇