什么是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容器的启动和关闭流程
简单版:
- 加载配置文件:Spring容器从配置文件中读取Bean的定义信息,并将其存储在内存中。
- 创建和注册Bean:根据配置文件中的Bean定义信息,Spring容器创建对应的Bean实例。
- 依赖注入:Spring容器根据Bean之间的依赖关系,将依赖的Bean实例注人到目标Bean中。
- 初始化Bean实例: 对Bean进行初始化操作,如调用初始化方法、执行自定义初始化逻辑等。
- 启动应用上下文:完成以上步骤后,Spring容器启动应用上下文,等待其他组件的请求。
- 销毁Bean: 当应用上下文关闭时,Spring容器会销毁所有Bean实例,释放资源。
- 销毁容器
启动
虽然ApplicationContext
继承自BeanFactory
,更确切地说是ApplicationContext
内部持有了一个实例化的BeanFactory(DefaultListableBeanFactory)
,BeanFactory
的相关操作其实是委托给这个实例来处理。
xml或者注解的形式启动容器
- xml:
- 调用父类的构造函数:如果有父容器就注册一下,以及创建一个处理xml的命名Patten的resourcepatternresolver。
- 用resourcepatternresolver处理xml文件名:resolver里有placeholderresolver支持文件名有${xxx}等的placeholder占位符。
- 调用refresh():是核心函数。
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
//如果已经有application Context ,并需要配置成父子关系, 调用该构造方法
super(parent);
// 根据提供的路径,处理成配置文件数组(以分号、逗号、空格、tab、换行符分割)
setConfigLocations(configLocations);
if (refresh) {
refresh();//核心!!!
}
}
- 注解:
- 调用本类的构造函数:创建beandefinition的reader和scanner。
- scan函数将被注解的类的相关配置转换成了beandefinition。
- 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();
}
}
}
关闭
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(); // 执行这个方法就会执行增强方法
-
-
-
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都是无状态的,可认为是线程安全的。
解决方案:
- bean中定义Threadlocal变量,将可变的成员变量放在Threadlocal中。(推荐)
- bean中尽量避免定义可变的成员变量。(不太现实)
bean的生命周期?
Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:
- Bean自身的方法: 这个包括了Bean本身调用的方法和通过配置文件中
<bean>
的init-method和destroy-method指定的方法 - Bean级生命周期接口方法: 这个包括了BeanNameAware、BeanFactoryAware、ApplicationContextAware;当然也包括InitializingBean和DiposableBean这些接口的方法(可以被@PostConstruct和@PreDestroy注解替代)
- 容器级生命周期接口方法: 这个包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
- 工厂后处理器接口方法: 这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
具体流程:
-
如果 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 进行销毁。
总体下来的大致流程是:
- 尝试从bean工厂中获取bean
- 根据配置情况调用bean的构造方法初始化bean
- 利用依赖注入(如setter方法)给bean注入属性值
- 调用各种XXXAware接口
- 如果配置了 init-method ,就执行该方法
- 使用
- 调用destroy方法销毁bean
- 如果配置了destory-method,就执行该方法
简单讲讲对SpringMVC的理解
-
M(模型) :数据,业务处理
-
V(视图):数据显示
-
C(控制器):接收请求,调用业务,返回数据给前端
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 工作流程是什么?
DispatcherServlet
接收用户的请求DispatcherServlet
调用HandlerMapping
,根据url找到对应的**Handler
,并连同请求涉及的拦截器**封装在一起返回。DispatcherServlet
调用HandlerAdapter
,根据找到的Handler去适配执行Handler。(上一步只是找,这一步就执行)- Handler执行完后返回数据对象(model)和逻辑视图(ModelAndView)给
DispatcherServlet
。 viewResolver
将逻辑视图解析渲染成真正的视图(view),并返回给DispatcherServlet
。DispatcherServlet
将model传给view。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) {
//......
}
}
ExceptionHandlerMethodResolver
中 getMappedMethod
方法决定了异常具体被哪个被 @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)
- 事务协调员:被调用方
spring事务传播行为有哪些?
spring事务传播行为是为了解决业务层之间方法的互相调用问题。例如,当一个事务方法被另一个事务方法所调用,可以指定被调用事务方法加入到调用事务中运行,也可以开启一个新事务在自己的事务中运行。可在@Transactional(propagation = xxx)
中用TransactionDefinition接口定义的常量设置。
一共有7种:
-
加入事务开启方(管理员):
-
PROPAGATION_REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
-
PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
-
PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。
-
-
不加入事务开启方(管理员):
-
PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。不管外部有没有事务,都新开自己的事务,且与外部事务独立,互不干扰。
-
PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。不管外部有没有事务,都不开事务,也不加入。
-
PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
-
-
其他情况:
PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。
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只能自定义针对 ParameterHandler
、 ResultSetHandler
、 StatementHandler
、 Executor
这 4 种接口的插件。
自定义插件流程:
- 创建一个类,实现拦截器Interceptor接口。
- 在该类上@Intercepts注解完成插件签名,指定对哪个对象的哪个方法进行拦截。
- 将写好的插件注册到全局配置文件中。
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 执行器?
有两种方法:
- 配置文件中指定:
mybatis:
executor-type: SIMPLE
- 手动给
DefaultSqlSessionFactory
的创建 SqlSession 的方法传递ExecutorType
类型参数。
MyBatis 映射文件中,如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面?
定义在哪里都可以正确解析。
原理是,MyBatis 解析 A 标签,发现 A 标签引用了 B 标签,但是 B 标签尚未解析到,尚不存在,此时,MyBatis 会将 A 标签标记为未解析状态,然后继续解析余下的标签,包含 B 标签,待所有标签解析完毕,MyBatis 会重新解析那些被标记为未解析的标签,此时再解析 A 标签时,B 标签已经存在,A 标签也就可以正常解析完成了。