Spring面试题

IOC

1.1 IOC的概念与原理

IOC,即控制反转,把对象的创建、初始化、销毁交给 Spring 来管理,而不是由开发者控制,实现控制反转。IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂(BeanFactory 接口)。IOC的原理是基于xml解析、工厂设计模式、反射实现的。使用IOC可以降低代码的耦合度。

1.2 Spring 提供 IOC 容器实现两种方式

(1)BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用。
在加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
(2)ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人
员进行使用。在加载配置文件时候就会把在配置文件对象进行创建.

1.3 IOC 操作 Bean 管理

​ IOC操作Bean管理有两种形式,一种是基于xml方式,另一种是基于注解方式。
​ 基于xml方式的Bean管理,Spring主要提供了等标签。
​ 基于注解方式的Bean管理,Spring主要提供了@Component @Service @Controller @Repository等注解。

AOP

AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,看到这其实应该明白了,AOP 其实就是前面一篇文章讲的代理模式的典型应用。
按照 AOP 框架修改源代码的时机,可以将其分为两类:

  • 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  • 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。

AOP应用场景

场景一: 记录日志

场景二: 监控方法运行时间 (监控性能)

场景三: 权限控制

场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )

场景五: 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )

AOP的实现原理

那Spring中AOP是怎么实现的呢?

AOP底层是使用动态代理来实现的,这里有两种情况的动态代理:

① 有接口的情况,使用 JDK 动态代理

即创建接口实现类代理对象,增强类的方法。

② 没有接口的情况,使用 CGLIB 动态代理

即创建子类的代理对象,增强类的方法。

事务

事务是一系列的动作,一旦其中有一个动作出现错误,必须全部回滚,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态,避免出现由于数据不一致而导致的接下来一系列的错误。事务的出现是为了确保数据的完整性和一致性,在目前企业级应用开发中,事务管理是必不可少的。

事务是逻辑上的一组操作,要么都执行,要么都不执行。

事务有四大特性(ACID)

1.原子性(Atomicity)事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。

2.一致性(Consistency)事务在完成时,必须是所有的数据都保持一致状态。

3.隔离性(Isolation)并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性。

4.持久性(Durability)一旦事务完成,数据库的改变必须是持久化的。

事务并发所可能存在的问题:
1.脏读:一个事务读到另一个事务未提交的更新数据。
2.不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。
3.幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
4.丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。

Spring事务管理

Spring事务管理的核心接口是PlatformTransactionManager

事务管理器接口通过getTransaction(TransactionDefinition definition)方法根据指定的传播行为返回当前活动的事务或创建一个新的事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。
TransactionDefinition接口中定义了它自己的传播行为和隔离级别

声明式事务管理

声明式事务管理有两种常用的方式,一种是基于tx和aop命名空间的xml配置文件,一种是基于@Transactional注解,随着Spring和Java的版本越来越高,大家越趋向于使用注解的方式

1.基于tx和aop命名空间的xml配置文件

配置文件

<tx:advice id="advice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="insert" propagation="REQUIRED" read-only="false"  rollback-for="Exception"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="pointCut" expression="execution (* com.gray.service.*.*(..))"/>
    <aop:advisor advice-ref="advice" pointcut-ref="pointCut"/>
</aop:config>

测试代码

@Test
public void transTest() {
    System.out.println("before transaction");
    Integer sum1 = baseSevice.sum();
    System.out.println("before transaction sum: "+sum1);
    System.out.println("transaction....");
    try{
        baseSevice.insert("INSERT INTO tbl_account VALUES (100);",true);
    } catch (Exception e){
        e.printStackTrace();
    }
    System.out.println("after transaction");
    Integer sum2 = baseSevice.sum();
    System.out.println("after transaction sum: "+sum2);
}

2.基于@Transactional注解

这种方式最简单,也是最为常用的,只需要在配置文件中开启对注解事务管理的支持。

<!-- 声明式事务管理 配置事物的注解方式注入-->
<tx:annotation-driven transaction-manager="transactionManager"/>

然后在需要事务管理的地方加上@Transactional注解,如:

@Transactional(rollbackFor=Exception.class)
public void insert(String sql, boolean flag) throws Exception {
    dao.insertSql(sql);
    // 如果flag 为 true ,抛出异常
    if (flag){
        throw new Exception("has exception!!!");
    }
}

rollbackFor属性指定出现Exception异常的时候回滚,遇到检查性的异常需要回滚,默认情况下非检查性异常,包括error也会自动回滚。
测试代码和上面那个一样

Spring框架中用到了哪些设计模式

1. 单例模式

单例模式应该是大家印象最深的一种设计模式了。在Spring中最明显的使用场景是在配置文件中配置注册bean对象的时候「设置scope的值为singleton」

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean class="com.dpb.pojo.User" id="user" scope="singleton">
  <property name="name" value="波波烤鸭"></property>
 </bean>
</beans>

单例模式有饿汉模式、懒汉模式、静态内部类、枚举等方式实现,但由于以上模式的构造方法是私有的,不可继承,Spring为实现单例类可继承,使用的是单例注册表的方式(登记式单例)。
什么是单例注册表呢,

登记式单例实际上维护的是一组单例类的实例,将这些实例存储到一个Map(登记簿)中,对于已经登记过的单例,则从工厂直接返回,对于没有登记的,则先登记,而后返回

使用map实现注册表;

使用protect修饰构造方法;

有的时候,我们不希望在一开始的时候就把一个类写成单例模式,但是在运用的时候,我们却可以像单例一样使用他

最典型的例子就是spring,他的默认类型就是单例,spring是如何做到把不是单例的类变成单例呢?

这就用到了登记式单例

其实登记式单例并没有去改变类,他所做的就是起到一个登记的作用,如果没有登记,他就给你登记,并把生成的实例保存起来,下次你要用的时候直接给你。

IOC容器就是做的这个事,你需要就找他去拿,他就可以很方便的实现Bean的管理。

懒汉式饿汉式这种通过私有化构造函数,静态方法提供实例的单例类而言,是不支持继承的。这种模式的单例实现要求每个具体的单例类自身来维护单例实例和限制多个实例的生成。可以采用另外一种实现单例的思路:登记式单例,来使得单例对继承开放。

懒汉式饿汉式的getInstance()方法都是无参的,返回本类的单例实例。而登记式单例是有参的,根据参数创建不同类的实例加入Map中,根据参数返回不同类的单例实例
我们看一个例子:

Import java.util.HashMap;    
Public class RegSingleton{
    //使用一个map来当注册表
   Static private HashMap registry=new HashMap();    
   //静态块,在类被加载时自动执行,把RegistSingleton自己也纳入容器管理    
    Static{    
     RegSingleton rs=new RegSingleton();    
     Registry.put(rs.getClass().getName(),rs);    
   }    
//受保护的默认构造函数,如果为继承关系,则可以调用,克服了单例类不能为继承的缺点    
Protected RegSingleton(){}    
//静态工厂方法,返回此类的唯一实例    
public static RegSingleton getInstance(String name){    
    if(name==null){    
      name=” RegSingleton”;    
    }if(registry.get(name)==null){    
      try{    
          registry.put(name,Class.forName(name).newInstance());    
       }Catch(Exception ex){ex.printStackTrace();}    
    }    
    Return (RegSingleton)registry.get(name);    
}    
}  

受保护的构造函数,不能是私有的,但是这样子类可以直接访问构造方法了

解决方式是把你的单例类放到一个外在的包中,以便在其它包中的类(包括缺省的包)无法实例化一个单例类。

看下spring的源码:

public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{    
   /**  
    * 充当了Bean实例的缓存,实现方式和单例注册表相同  
    */    
   private final Map singletonCache=new HashMap();    
   public Object getBean(String name)throws BeansException{    
       return getBean(name,null,null);    
   }    
...    
   public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{    
      //对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或则做转码)    
      String beanName=transformedBeanName(name);    
      Object bean=null;    
      //手工检测单例注册表    
      Object sharedInstance=null;    
      //使用了代码锁定同步块,原理和同步方法相似,但是这种写法效率更高    
      synchronized(this.singletonCache){    
         sharedInstance=this.singletonCache.get(beanName);    
       }    
      if(sharedInstance!=null){    
         ...    
         //返回合适的缓存Bean实例    
         bean=getObjectForSharedInstance(name,sharedInstance);    
      }else{    
        ...    
        //取得Bean的定义    
        RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);    
         ...    
        //根据Bean定义判断,此判断依据通常来自于组件配置文件的单例属性开关    
        //<bean id="date" class="java.util.Date" scope="singleton"/>    
        //如果是单例,做如下处理    
        if(mergedBeanDefinition.isSingleton()){    
           synchronized(this.singletonCache){    
            //再次检测单例注册表    
             sharedInstance=this.singletonCache.get(beanName);    
             if(sharedInstance==null){    
                ...    
               try {    
                  //真正创建Bean实例    
                  sharedInstance=createBean(beanName,mergedBeanDefinition,args);    
                  //向单例注册表注册Bean实例    
                   addSingleton(beanName,sharedInstance);    
               }catch (Exception ex) {    
                  ...    
               }finally{    
                  ...    
              }    
             }    
           }    
          bean=getObjectForSharedInstance(name,sharedInstance);    
        }    
       //如果是非单例,即prototpye,每次都要新创建一个Bean实例    
       //<bean id="date" class="java.util.Date" scope="prototype"/>    
       else{    
          bean=createBean(beanName,mergedBeanDefinition,args);    
       }    
}    
...    
   return bean;    
}    
}

2. 原型模式

原型模式也叫克隆模式,Spring中该模式使用的很明显,和单例一样在bean标签中设置scope的属性prototype即表示该bean以克隆的方式生成

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean class="com.dpb.pojo.User" id="user" scope="prototype">
  <property name="name" value="波波烤鸭"></property>
 </bean>
</beans>

3. 模板模式

模板模式的核心是父类定义好流程,然后将流程中需要子类实现的方法就抽象话留给子类实现,Spring中的JdbcTemplate就是这样的实现。我们知道jdbc的步骤是固定

  • 加载驱动,
  • 获取连接通道,
  • 构建sql语句.
  • 执行sql语句,
  • 关闭资源

在这些步骤中第3步和第四步是不确定的,所以就留给客户实现,而我们实际使用JdbcTemplate的时候也确实是只需要构建SQL就可以了.这就是典型的模板模式。

4. 观察者模式

观察者模式定义的是对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。使用比较场景是在监听器中而spring中Observer模式常用的地方也是listener的实现。如ApplicationListener.

5. 工厂模式

「简单工厂模式」

简单工厂模式就是通过工厂根据传递进来的参数决定产生哪个对象。Spring中我们通过getBean方法获取对象的时候根据id或者name获取就是简单工厂模式了。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

 <context:annotation-config/>
 <bean class="com.dpb.pojo.User" id="user"  >
  <property name="name" value="波波烤鸭"></property>
 </bean>
</beans>

「工厂方法模式」

在Spring中我们一般是将Bean的实例化直接交给容器去管理的,实现了使用和创建的分离,这时容器直接管理对象,还有种情况是,bean的创建过程我们交给一个工厂去实现,而Spring容器管理这个工厂。这个就是我们讲的工厂模式,在Spring中有两种实现一种是静态工厂方法模式,一种是动态工厂方法模式。以静态工厂来演示

/**
 * User 工厂类
 * @author dpb[波波烤鸭]
 *
 */
public class UserFactory {

 /**
  * 必须是static方法
  * @return
  */
 public static UserBean getInstance(){
  return new UserBean();
 }
}

application.xml文件中注册

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
 <!-- 静态工厂方式配置 配置静态工厂及方法 -->
 <bean class="com.dpb.factory.UserFactory" factory-method="getInstance" id="user2"/>
</beans>

6. 适配器模式

将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。这就是适配器模式。在Spring中在AOP实现中的Advice和interceptor之间的转换就是通过适配器模式实现的。

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

 @Override
 public boolean supportsAdvice(Advice advice) {
  return (advice instanceof MethodBeforeAdvice);
 }

 @Override
 public MethodInterceptor getInterceptor(Advisor advisor) {
  MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
  // 通知类型匹配对应的拦截器
  return new MethodBeforeAdviceInterceptor(advice);
 }
}

7. 装饰者模式

装饰者模式又称为包装模式(Wrapper),作用是用来动态的为一个对象增加新的功能。装饰模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。 spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。 具体的使用在Spring session框架中的SessionRepositoryRequestWrapper使用包装模式对原生的request的功能进行增强,可以将session中的数据和分布式数据库进行同步,这样即使当前tomcat崩溃,session中的数据也不会丢失。

<dependency>
 <groupId>org.springframework.session</groupId>
 <artifactId>spring-session</artifactId>
 <version>1.3.1.RELEASE</version>
</dependency>

8. 代理模式

代理模式应该是大家非常熟悉的设计模式了,在Spring中AOP的实现中代理模式使用的很彻底.

9. 策略模式

策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便的更换算法或者增加新的算法。并且由客户端决定调用哪个算法,spring中在实例化对象的时候用到Strategy模式。XmlBeanDefinitionReader,PropertiesBeanDefinitionReader

10. 责任链默认

AOP中的拦截器链

11. 委托者模式

DelegatingFilterProxy,整合Shiro,SpringSecurity的时候都有用到。

..... 等

posted @   雾霭雾海  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示