目录
2. IOC是怎么实现的 / Spring的Bean是如何加载的 / 生命周期是什么样的?
3. BeanFactory和ApplicationContext有什么区别?
5. Spring中的单例Bean是线程安全的吗?Spring如何解决线程并发问题?
7. Autowired和Resource有什么区别?RequestMapping有什么作用?
5. mapper接口是怎么找到mapper.xml中对应的sql的?
一. Spring
1. 简单说一下Spring
Spring是一个轻量级java开发框架,简化了java开发。Spring有两大核心,IOC和AOP。
- IOC是控制反转,把对象交给Spring来管理。IOC支持依赖注入(DI),可以动态的给一个对象注入它需要依赖的对象。
- AOP是面向切面编程,横向抽取公共逻辑,封装成模块。可用于权限控制,日志处理等。
2. IOC是怎么实现的 / Spring的Bean是如何加载的 / 生命周期是什么样的?
IOC容器本质上是一个Map,key是bean名称,value是bean实例。bean加载到IOC容器的过程主要是:bean实例化、属性设置、初始化、销毁
- Spring容器会先扫描xml文件或bean定义注解拿到所有的beanDefination,然后把beanDefination放到map中,当容器初始化的时候,用反射的方式把beanDefination实例化为bean
- 然后再进行属性注入,主要是注入bean的基本类型、引用类型等属性
- 然后再进行bean初始化,@PostConstruct注解标注的方法就是bean的初始化方法,一般会执行加载资源、连接数据库、建立网络连接等操作。
- 这样就完成了bean的加载,spring会把加载完成bean放到ConcurrentHashMap中,我们就可以直接从容器中获取bean了,最后在容器销毁的时候会把bean也销毁
3. BeanFactory和ApplicationContext有什么区别?
- BeanFactory和ApplicationContext都可以作为Spring的容器,BeanFactory是最底层的接口,ApplicationContext是BeanFactory的派生。
- BeanFactory是延迟加载,使用时才会加载,如果Bean的某一个属性没有注入,只有第一次使用时才出抛出异常。ApplicationContext在容器启动时,就会全部加载所有的Bean,虽然有些耗费内存,但是需要使用时无需等待。
4、ApplicationContext的实现类有哪些?
- FileSystemXmlApplicationContext:此容器用xml的方式加载beans定义,需提供xml文件绝对路径
- ClassPathXmlApplicationContext:此容器用xml的方式加载beans定义,需提供classpath和相对路径
- WebXmlApplicationContext:此容器加载一个Xml文件,该文件定义了WEB应用的所有Bean
- AnnotationConfigApplicationContext:用注解的方式启动容器
5. Spring中的单例Bean是线程安全的吗?Spring如何解决线程并发问题?
不安全,但是大多数Bean都是无状态的,也就是不存储数据,所以某种程度上说不存在线程安全问题。
对于需要存储数据的Bean,我们可以把共享变量设为ThreadLocal变量,那么访问这个变量的每个线程都会有一个变量副本,实际操作的时候,也是使用的本地变量副本,从而规避了线程安全问题。
7. Autowired和Resource有什么区别?RequestMapping有什么作用?
Autowired是按类型装配,Resource是按名称装配,名称不存在时按类型装配。RequestMapping用来映射URL。
8. SpringAOP了解吗?AOP有哪些实现方式?
SpringAOP是通过代理模式来实现的。代理模式分为静态代理和动态代理。
- AspectJ就是静态代理,静态代理是在编译时期就把通知织入字节码文件中,运行的直接是增强后的字节码对象。
- SpringAOP是动态代理,是在运行时动态的生成一个AOP对象,这个对象包含了被代理对象的全部方法,并且在特定切点做了增强处理,并回调原对象的方法。
Spring动态代理主要有2中实现方式,JDK动态代理和CGLIB动态代理。
- JDK动态代理只能代理接口,不能代理类。实现方式:代理对象实现InvocationHandler,把被代理对象的接口从构造函数传入,然后用invoke接口实现增强,利用反射调用原对象里的方法。使用的时候,使用Proxy.newProxyInstatnce(代理类类加载器,被代理接口,被代理实现类)生成代理类,就可以调用方法了。
- CGLIB可以代理类,主要是通过继承的方式来实现。实现方式:代理对象实现MethodInterceptor,对方法进行拦截处理。使用的时候,使用Enhance的setSuperClass(被代理类),setCallBack(代理类),最后用Enhance的create()来创建被代理类,就可以直接使用被代理类调用方法了。
9. 过滤器和拦截器有什么区别?
- 实现原理上,过滤器是用回调实现的,拦截器是用动态代理实现的;
- 使用范围上,过滤器必须实现java.servlet.filter接口,必须依赖tomcat等容器,拦截器在Spring包下,由Spring管理;
- 触发时机上,过滤器在servlet前后执行,拦截器在controller前后执行;
- 应用场景上,过滤器主要是对请求做处理,比如拦截非法参数、无效请求、黑白名单;拦截器主要是业务类的操作,比如权限校验(管理员、普通用户),日志记录。
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化操作
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 执行过滤操作
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 销毁操作
}
}
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在处理器执行之前执行的逻辑
return true; // 返回 true 表示继续执行,返回 false 表示终止执行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在处理器执行之后,视图渲染之前执行的逻辑
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在请求完成之后执行的逻辑,无论结果如何都会执行
}
}
10. Spring是如何解决循环依赖问题的?
构造器注入的循环依赖是没有办法解决的,Spring解决了属性注入方式的循环依赖问题。比如A依赖B,B又依赖A,Spring会先在三级缓存中获取A是否存在,此时A肯定不存在,于是实例化A(因为B是以@Autowired注入的,所以此时并不会触发实例化B),并把A放入三级缓存中,然后进行populateBean(属性注入),此时实例化B,三级缓存中B也不存在,于是实例化B并放入三级缓存,再进行属性注入,此时三级缓存中有A,拿到A进行属性注入。
为什么要有三级缓存?两级不行吗?
Spring 的循环依赖:真的必须非要三级缓存吗?_Java知音_的博客-CSDN博客
- 第一级缓存存的是对外暴露的对象,也就是我们应用需要用到的
- 第二级缓存的作用是为了处理循环依赖的对象创建问题,里面存的是半成品对象或半成品对象的代理对象
- 第三级缓存的作用处理存在 AOP + 循环依赖的对象创建问题,能将代理对象提前创建
12. Spring是怎么扫描注解或者xml文件的?
根据@ComponentScan配置的扫描路径,扫描这个路径下的注解,比如@Controller@Service@Repository@Component等,然后用反射的方式实例化被注解的类,并放入容器中。
13. 怎么自定义一个Spring注解?
定义注解就用@interface自定义一个注解,上边再加上一些元注解,比如@Target、@Retention、@Documented、@Inherited
元注解:
- @Target表示注解的使用范围,包括类、方法、属性;
- @Retention表示被它注解的注解类注解到别的类上时,可以保留到何时,包括源文件,编译器,运行时;
- @Documented表示生成文档时是否要保留注解信息;
- @Inherited注解表示被@Inherited注解的注解被注解到其他类上时,其子类自动可以拥有该注解。
14. 怎么用AOP实现一个日志拦截或者异常拦截?
有两种方式,一种是用切点的方式,一种是注解的方式。
切点的方式就是在@PointCut后边配置类路径,这个类路径下的所有方法都会被增强。
注解的方式就是在@Around@Before这种注解后边配置一个注解,所有被注解的类都会被增强。
方式一:切点方式
用@Aspect注解的类就是一个切面,@Pointcut是一个切点,切点后边配置的是类路径,这个路径中所有方法都会被增强,@Around就是增强,joinPoint可以拿到所有切点中的方法,然后在切点前后做一些操作
方式二:注解的方式
这里表示被@EnableTimerLog注解的所有类都会走到这个@Around注解的增强里,然后就可以在joinPoint.proceed()前后做处理。
15. Spring中的设计模式
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- ⼯⼚设计模式 : Spring使⽤⼯⼚模式通过 BeanFactory 、 ApplicationContext 创建bean 对象。
- 代理设计模式 : Spring AOP 功能的实现。
- 模版方法:父类定义骨架(调用哪些方法及顺序),子类实现特定的方法。Spring中的JdbcTamplate就是用的模版方法
- 观察者模式: Spring 事件监Listener就是观察者模式很经典的⼀个应⽤。
- 适配器模式:Spring MVC 中也是⽤到了适配器模式适配 Controller 。每个Controller都有对应的HandlerAdapter,后续每扩展一个Controller,只需增加一个对应的HandlerAdapter即可。
16. Spring中常用的扩展性的接口有哪些?
- InitializingBean:想在Bean初始化完成后做一些操作,就可以使用该接口,或@PostConstruct注解
- DisposableBean:想在bean销毁前执行自定义销毁方法,就可以使用该接口,或@PreDestroy注解
- Aware接口:比如BeanFactoryAware、ApplicationContextAware等,用于获取Spring容器上下文信息
- Listener接口:比如ApplicationListener,用于监听应用程序中的事件。底层是观察者模式
@Component
public class MyBean implements ApplicationContextAware {
private ApplicationContext applicationContext;
// 在bean实例化后、属性赋值前被调用
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void doSomething() {
// 使用获取到的 ApplicationContext 对象
SomeOtherBean otherBean = applicationContext.getBean(SomeOtherBean.class);
// 其他操作...
}
}
/**
* SmsListener短信监听器:监听订单创建时间
*/
@Component
public class SmsListener implements ApplicationListener<OrderCreateEvent> {
@Override
public void onApplicationEvent(OrderCreateEvent event) {
//. 发送短信: 调用短信服务,给手机号发送短信信息.
System.out.println("发送短信 - 调用短信服务,给手机号发送短信信息;订单信息:"+event.getOrderInfo());
}
}
/**
* WechatListener微信监听器:监听订单创建时间
*/
@Component
public class WechatListener implements ApplicationListener<OrderCreateEvent> {
@Override
public void onApplicationEvent(OrderCreateEvent event) {
//. 发送微信 - 调用微信公众号的通知服务,进行发送。
System.out.println("发送微信 - 调用微信公众号的通知服务,进行发送;订单信息:"+event.getOrderInfo());
}
}
17. Java SPI、Spring SPI、Dubbo SPI有什么区别?
SPI(Service Provider Interface)是一种服务发现机制,它让API提供者只提供接口,第三方来具体实现和扩展。这种设计就是在不修改自身代码的情况下,通过第三方实现来增强功能。
Java SPI是用ServiceLoader实现的。首先Java SPI约定了两件事
- 配置文件必须放在META-INF/services/目录下
- 配置文件名必须为接口的全限定类名,内容为实现的全限定类名
然后就可以通过ServiceLoader中传入的接口名找到对应的配置文件,以及该接口对应的实现类的全限定类名,再通过反射实例化对象
//在META-INF/services/目录创建一个文件名LoadBalance全限定名的文件,文件内容为RandomLoadBalance的全限定名 // 然后定义如下demo public class ServiceLoaderDemo { public static void main(String[] args) { ServiceLoader<LoadBalance> loadBalanceServiceLoader = ServiceLoader.load(LoadBalance.class); Iterator<LoadBalance> iterator = loadBalanceServiceLoader.iterator(); while (iterator.hasNext()) { LoadBalance loadBalance = iterator.next(); System.out.println("获取到负载均衡策略:" + loadBalance); //这里的loadBalance就是RandomLoadBalance,打印的是RandomLoadBalance的全限定类名 } } }
运行结果:
Spring SPI是用SpringFactoriesLoader实现的,也有两个要求
- 配置文件必须在META-INF/目录下,文件名必须为spring.factories
- 文件内容为键值对,用等号连接,键是接口全限定类名,值是实现类全限定类名。一个键可以有多个值,用逗号分隔即可
然后就可以通过SpringFactoriesLoader中传入的接口名在spring.factories文件中找到对应的实现类的全限定类名,再通过反射实例化对象
Spring的自动装配就是使用 Spring SPI。
配置文件:
测试:
public class SpringFactoriesLoaderDemo { public static void main(String[] args) { List<LoadBalance> loadBalances = SpringFactoriesLoader.loadFactories(LoadBalance.class, MyEnableAutoConfiguration.class.getClassLoader()); for (LoadBalance loadBalance : loadBalances) { System.out.println("获取到LoadBalance对象:" + loadBalance); } } }
运行结果:
Dubbo SPI是通过ExtensionLoader实现的。要求如下:
- 接口必须加@SPI注解
- 配置文件可以放在META-INF/services/、META-INF/dubbo/下,文件名是接口的全限定类名
- 配置文件的内容为键值对,键为短名称(类似于Spring中bean的名称),值为实现类的全限定类名。
ExtensionLoader也是使用Java的ServiceLoader找到接口对应的实现类,再使用反射实例化对象。
Dubbo中的负载均衡就是使用Dubbo SPI实现的。它提供了LoadBalance接口,可以在配置文件中配置对应的实现类。
t阿里一面:说一说Java、Spring、Dubbo三者SPI机制的原理和区别-51CTO.COM
主流框架都用SPI机制,看一下他们的区别和原理_ssm_Java你猿哥_InfoQ写作社区
二. SpringMVC
1. 描述一下SpringMVC的工作流程
- 用户发送一条请求到DispatcherServlet(控制器),DispatcherServlet访问HandleMapping(处理器映射器),获得Handler;
- DispatcherServlet拿着Handler调用HandlerAdapter(处理器适配器),HandlerAdapter负责执行具体的Handler业务逻辑,并返回ModelAndView;
- DispatcherServlet拿着ModelAndView访问viewResolever(视图解析器),viewResolever解析完视图并返回给DispatcherServlet;
- DispatcherServlet再请求视图进行渲染,最终给用户返回视图。
三. Mybatis
1. 为什么要使用Mybatis?
- 使用Mybatis不用再手动创建连接,只需把数据库连接信息配置到配置文件中,Mybatis会创建连接池去连接数据库
- 把sql语句和java代码隔离开了,改动sql也不影响java代码
- Mybatis比hibernate更轻量,开发者也不用专门再去学HQL
Mybatis:轻量级框架,学习成本低,适合需求频繁变动的项目,需求变动只需改sql语句
Hibernate:重量级框架,需要学习HQL,适合需求不怎么变动的项目,需求变动需要改java代码
2. Mybatis原理?
Mybatis中的mapper是接口,为什么调用接口中的方法就能执行sql语句,并拿到返回值呢?这主要是因为Mybatis使用了动态代理,对mapper接口生成了它的代理对象,具体执行步骤如下:
- 当调用mapper中的方法时,实际上是调用代理对象中的方法。代理对象首先会根据mapper的类名和xml文件中namespace进行匹配找到对应的mapper.xml文件,再根据方法名和xml文件中sql语句的id匹配找到对应的sql语句,并把sql语句和参数拼接完整;
- 然后获取数据库连接,并把sql语句发送给数据库;
- 拿到sql语句的返回值后,将其和resultMap中的Java对象做映射,<column>列名和<properties>属性进行对应,最后将java对象返回给用户。
MyBatis是如何把sql执行结果映射成对象的?
通过<resultMap>标签,里边映射<properties>属性 和<column>列名
3. mapper接口是怎么找到mapper.xml中对应的sql的?
Mapper.xml文件中的namespace即是mapper接口的类路径,xml中sql语句的id和接口中的方法名一致。
4. #{} 和 ${} 有什么区别?
#{} 和 ${} 都可以传入变量值,如#{userName}。
区别是变量替换后,#{} 会给变量自动加上' '单引号,但${} 不会。因此#{} 可以防止sql注入,${}不行。
5. Mybatis的映射文件中如何遍历?
<foreach collection="statusList" item="status" index="index" open="(" close=")" separator=",">
#{status}
</foreach>
6. Mybatis一级缓存和二级缓存?
讲解:MyBatis 一级缓存、二级缓存_chy1984的博客-CSDN博客
SqlSession主要是用来执行增删改查方法的,sqlSessionFactory用于创建sqlSession实例
Mybatis的缓存作用是:如果要查询的内容在缓存中存在,则直接从缓存中取,否则查询数据库。
- 一级缓存是sqlSession缓存,默认开启,在同一个sqlSession的多个查询操作间可以共享缓存,当这个sqlSession中有增、删、改操作时,会自动清空缓存。sqlSession关闭也会清空缓存
- 二级缓存是sqlSessionFactory缓存,在多个sqlSession中共享,默认关闭,因为缓存时间长,容易出现脏读问题,所以一般不用
##一级缓存的生命周期在sqlSession的生命周期之内,即在mapper方法调用的生命周期之内。
//一个sqlSession,open-> close,方法调用结束时一级缓存会被清空
userMapper.findUserByName();
//一个新的sqlSession
userMapper.findUserByName();
7. Mybatis是否支持延迟加载?
支持。可以在配置文件中配置lazyLoadingEnable=true|false。延迟加载指的是当使用到某个对象的属性时才去执行sql语句,而不是直接执行。
原理是:mybatis使用动态代理实现了对延迟加载的支持。mybatis查询时会返回一个代理对象而不是实际的数据对象,该代理对象会i拦截所有的访问操作。当访问这个代理对象的某个属性时,会判断当前属性是否已经被加载,是则直接返回,不是则查询数据库并返回结果。