Java面试题(10)- Spring框架

Java面试题(10)- Spring框架

1 谈谈Spring Bean的作用域和生命周期?

Spring为Bean定义了5种作用域,分别为singleton(单例)、prototype(原型)、request、session和global session。
singleton:单例模式,Spring IoC容器中只会存在一个共享的Bean实例,无论有多少个Bean引用它,始终指向同一对象。Singleton作用域是Spring中的默认作用域,也可以显式的将Bean定义为singleton模式,配置为:

<bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>

prototype:原型模式,每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
request:在一次Http请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效。

<bean id="loginAction" class="com.cnblogs.Login" scope="request"/>

针对每一次Http请求,Spring容器根据该bean的定义创建一个全新的实例,且该实例仅在当前Http请求内有效,而其它请求无法看到当前请求中状态的变化,当当前Http请求结束,该bean实例也将会被销毁。
session:在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。

<bean id="userPreference" class="com.ioc.UserPreference" scope="session"/>

同Http请求相同,每一次session请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的session请求内有效,请求结束,则实例将被销毁。
global Session:在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。

Spring容器可以管理singleton作用域下Bean的生命周期。在此作用域下,Spring能够精确地知道Bean何时被创建,何时初始化完成,以及何时被销毁。而对于prototype作用域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的Bean的生命周期。Spring Bean的完整生命周期从创建Spring容器开始,直到最终Spring容器销毁Bean,这其中包含了一系列关键点。
Spring Bean生命周期
Spring Bean生命周期

Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:
(1)Bean自身的方法:这个包括了Bean本身调用的方法和通过配置文件中\<bean>的init-method和destroy-method指定的方法
(2)Bean级生命周期接口方法:这个包括了BeanNameAware)BeanFactoryAware)InitializingBean和DiposableBean这些接口的方法
(3)容器级生命周期接口方法:这个包括了InstantiationAwareBeanPostProcessor 和BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
(4)工厂后处理器接口方法:这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器  接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。

2 说说SpringMVC处理请求的流程?

Spring MVC,即模型 model, 视图 view, 控制器 controller。三者的工作流程如下:
(1)客户端发起HTTP请求:客户端将请求提交到DispatcherServlet。
(2)寻找处理器:由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理该请求的Controller。
(3)调用处理器:DispatcherServlet将请求提交到Controller。
(4)调用业务处理逻辑并返回结果:Controller调用业务处理逻辑后,返回ModelAndView。
(5)处理视图映射并返回模型:DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图。
(6)HTTP响应:视图负责将结果在客户端浏览器上渲染和展示。
SpringMVC流程
3 如何用Spring实现一个切面?

假设要实现的切面功能是打印方法名,首先定义一个切面注解@PrintMethodName,它可以使用在Class和Method上。

package com.test.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PrintMethodName {

}

然后实现切面逻辑,在Class上加上@Aspect注解,标注这是一个切面实现,要加上@Component注解,否则不会被Spring识别。

package com.test.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class PrintMethodNameAspect {

    @Pointcut("@within(com.test.aspect.PrintMethodName) || @annotation(com.test.aspect.PrintMethodName)")
    public void pointcut() {

    }

    @Around("pointcut()")
    public Object process(ProceedingJoinPoint point) throws Throwable {
        System.out.println(point.getSignature().getName());
        return point.proceed();
    }

}

其中的代码定义了一个检查点(Pointcut),@within表示在PrintMethodName注解所标记的Class内,@annotation表示被PrintMethodName注解标记的方法。无论将切面注解标注在Class上还是Method上,都可以拦截到。

Pointcut("@within(com.test.aspect.PrintMethodName) || @annotation(com.test.aspect.PrintMethodName)")

下面定义一个简单的实现类:

package com.test.aspect;

import org.springframework.stereotype.Component;

@Component
public class AspectService {

    @PrintMethodName
    public void firstMethod() {
        System.out.println("Into the first method.");
    }

    @PrintMethodName
    public void secondMethod() {
        System.out.println("Into the second method.");
    }
}

方法firstMethod、secondMethod加上了@PrintMethodName注解,下面是测试用例代码:

package com.test.aspect;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

public class AspectTestCase {

    @Autowired
    private AspectService aspectService;

    @Test
    public void testPrintMethod() {
        aspectService.firstMethod();
        aspectService.secondMethod();
    }

}

输出结果:

firstMethod
Into the first method.
secondMethod
Into the second method.

4 Spring AOP的原理是什么?

面向切面编程(Aspect Oriented Programming)通过预编译方式和运行期间的动态代理实现程序功能的统一维护的一种技术,这种结构来弥补了面向对象编程(Object Oriented Programming)的不足。AOP还提供了切面,对关注点进行模块化,使用模块化对业务逻辑的各个部分隔离,从而使得各部分业务逻辑直接的耦合度降低,提高程序的可重用性。Spring AOP是是Spring的重要组件之一,我们可以通过AOP完成对事务管理、权限控制、日志监听等。

Spring AOP是基于动态代理实现的,可以实现:动态创建一组指定的接口的实现对象。Spring AOP同时支持JDK Proxy和cglib。

JDK Proxy是通过复制原有代理类,然后生成一个新类。在生成新类的同时,将方法的调用转给了InvocationHandler,在代理类执行方法时,实际上是调用了InvocationHandler的invoke方法。

/**
 * 使用Java动态代理类
 */
class MyHandler implements InvocationHandler {
    private final Object target;
    public MyHandler(final Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        System.out.println("-----before----");
        final Object o = method.invoke(this.target, args);
        System.out.println("-----after----");
        return o;
    }
}

public static void main(final String[] args) {
        final BizAImpl bizAImpl = new BizAImpl();
        final IBizA newBizA = (IBizA)Proxy.newProxyInstance(MyHandler.class.getClassLoader(),
                bizAImpl.getClass().getInterfaces(),
                new MyHandler(bizAImpl));
        newBizA.doSomething();
}

cglib是通过asm库实现了代理class的生成,cglib会生成两个类,一个类是作为代理类使用,一个类是作为方法调用时使用。在调用方法的时候,cglib做了一些优化,没有采用反射调用方法,而是给每个代理方法增加一个索引,然后根据索引找到方法,并直接调用。

/**
 * 使用cglib实现动态代理类
 */
class MyHandler implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("----before----");
        proxy.invokeSuper(obj, args);
        System.out.println("----after----");
        return obj;
    }
}

public static void main(String[] args) {
    MyHandler myHandler = new MyHandler();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(BizA.class);
    enhancer.setCallback(myHandler);

    BizA bizA = (BizA) enhancer.create();
    bizA.doSomething();
}

5 Spring事务的传播属性是什么?

Spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法:
int getPropagationBehavior():事务的传播行为
int getIsolationLevel():事务的隔离级别
int getTimeout():事务的过期时间
boolean isReadOnly():事务的读写特性。

DefaultTransactionDefinition可以看到默认配置:

private int propagationBehavior = PROPAGATION_REQUIRED;

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring 支持 7 种事务传播行为:
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

6 Spring中BeanFactory和FactoryBean有什么区别?

BeanFactory,以Factory结尾,表示它是一个工厂类(接口),用于管理Bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。例如很常见的:ApplicationContext,XmlBeanFactory 等等都使用了BeanFactory这个接口。

FactoryBean以Bean结尾,表示它是一个Bean,不同于普通Bean的是:它是实现了FactoryBean\<T>接口的Bean,根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个\&符号来获取。

FactoryBean是工厂类接口,当你只是想简单的去构造Bean,不希望实现原有大量的方法。它是一个Bean,不过这个Bean能够做为工厂去创建Bean,同时还能修饰对象的生成。FactoryBean比BeanFactory在生产Bean的时候灵活,还能修饰对象,带有工厂模式和装饰模式的意思在里面,不过它的存在还是以Bean的形式存在。

简单理解的话,BeanFactory就是IOC容器,负责创建Bean,管理Bean;FactoryBean是被IOC容器管理的Bean,它有自己特殊的功能,比如生产一个B+对象,这个对象可能就是基于B对象,在通过FactoryBean生产的时候做了一些增加或者个性化设置。

7 Spring框架IoC的原理是什么?

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

在系统运行中,IoC动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,Spring就是通过反射来实现注入的。
  
8 Spring的依赖注入有哪几种方式?

Spring中依赖注入有四种方式:set注入(也叫属性注入),构造函数注入,接口注入(现在基本不用),注解注入(@Autowire)。下面对set方法注入,构造函数注入,以及注解注入的用法举例说明。

  • set方法注入

UserDao.java

public class UserDao{
    public void inserUser(User user){
             //具体逻辑省略
    }
}

UserService.java

public Interface UserService{
    void inserUser(User user);
}

UserServiceImpl.java

public class UserServiceImpl implements UserService{
    private UserDao userDao;    
    public void setUserDao(UserDao userDao){        
            this.userDao = userDao;
    }
     public void insertUser(User user){
        userDao.insert(user);
    }
}

Spring配置文件

<?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-2.5.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context-2.5.xsd ">
 <!--省略一些无关的配置书写-->
 <bean id="userDao" class="UserDao"></bean>
 <bean id="userService" class="UserServiceImpl">
 <property name="userDao" ref="userDao">
 </bean>
 </beans>
  • 构造函数注入

User.java

public class User{    
    //为了简便,就写两个属性
    private String name;    
    private Integer age;    
   //关于name,age的getter/setter方法省略
    public User(String name,int age){
        this.name = name;
        this.age = age;
    }
}

通过Spring配置文件来注入User对象

<?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-2.5.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context-2.5.xsd ">
 <!--省略一些无关的配置书写-->
 <bean id="user" class="User">
     <!--构造函数参数的下标从0开始(避免出现多个构造函数具有相同的参数个数)-->
     <constructor-arg type="java.lang.String" index="0" value="zhangsan"/>
     <constructor-arg type="java.lang.Integer" index="1" value="20>
 </bean>
 </beans>
  • 注解注入

UserDao.java

@Component
public class UserDao{    
    public void inserUser(User user){       
         //具体逻辑省略
    }
}

UserService.java

public Interface UserService{    
    void inserUser(User user);
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService{
    @Autowire
    private UserDao userDao;   

    public void insertUser(User user){
        userDao.insert(user);
    }
}

9 Spring如何实现数据库事务?

  • 注解方式

(1)声明事务管理器PlatformTransactionManager的实现类,通常使用DataSourceTransactionManager的实现类。在配置文件中使用bean标签声明:

<bean id="transactionManager"  class="xxx.xxx.xxx.DataSourceTransactionManager">
   <property name="dataSource" value="dataSource">
</bean>

(2)开启注解驱动

<tx:annotation-driver transaction-Manager="transactionManager"/>

(3)在目标方法上(也可以是目标类)加入@Transactional注解(propagation=propagation.require,isolation=isolation.DEFAULT,rollbackFor={NotNullException.class,NoPointException.class}….)

  • 使用aspectJ的AOP配置

(1)加入 aspectj 的依赖坐标

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

(2)声明事务管理器,通常使用DataSourceTransactionManager   

<bean id="transactionManager"  class="xxx.xxx.xxx.DataSourceTransactionManager">
   <property name="dataSource" value="dataSource">
</bean>

(3)配置事务通知,事务要织入到哪些方法上,以及事务的管理参数是哪些(传播属性,回退级别,安全级别等)

<advice  id="myAdvice" transaction-Manager="transactionManager">
    <tx:attributes>
        <tx:method="buySomeThing"   propagation="REQUIRED" isolation="DEFAULT"  rollbackFor=""/>
        <tx:method="buy*"……/>
        <tx:method="*"……/>
    </tx:attributes>
</advice>

(4)配置增强器,说明事务通知要作用给谁

<aop:conf>
    <aop:pointcut expression="execution("* *..service..*.*(..)")"  id="servletPt"/>
    <aop:advisor advice-ref="myAdvice"  point-ref="servletPt" />
</aop:conf>

参考
https://www.cnblogs.com/zhanglei93/p/6231882.html
https://juejin.cn/post/6847902217135980557

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注