第四篇:SSM框架(Spring、SpringMVC、Mybatis)

目录

一. Spring

1. 简单说一下Spring

2. IOC是怎么实现的 / Spring的Bean是如何加载的 / 生命周期是什么样的?

3. BeanFactory和ApplicationContext有什么区别?

4、ApplicationContext的实现类有哪些?

5. Spring中的单例Bean是线程安全的吗?Spring如何解决线程并发问题?

7. Autowired和Resource有什么区别?RequestMapping有什么作用?

8. SpringAOP了解吗?AOP有哪些实现方式?

9. 过滤器和拦截器有什么区别?

10. Spring是如何解决循环依赖问题的?

12. Spring是怎么扫描注解或者xml文件的?

13. 怎么自定义一个Spring注解?

14. 怎么用AOP实现一个日志拦截或者异常拦截?

15. Spring中的设计模式

二. SpringMVC

1. 描述一下SpringMVC的工作流程

三. Mybatis

1. 为什么要使用Mybatis?

2. Mybatis原理?

3. #{} 和 ${} 有什么区别?

4. Mybatis的映射文件中如何遍历?

5. mapper接口是怎么找到mapper.xml中对应的sql的?

6. Mybatis一级缓存和二级缓存?

7. Mybatis get()和load()的区别?


一. 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拦截所有的访问操作。当访问这个代理对象的某个属性时,会判断当前属性是否已经被加载,是则直接返回,不是则查询数据库并返回结果。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值