面试题----spring

Spring是什么?

  spring 可以看成一个大的集合,他由许多开源框架和组件组成,是一个统称。而我们平常学习的是springframework,springframework 是sping 里面的一个开源框架。

  springframework主要由以下几个模块组成:  

    Spring Core:核心类库,提供IOC服务,Spring AOP:AOP服务;Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);数据访问(jdbc,对现有的orm框架支持),Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;Spring MVC:提供面向Web应用的Model-View-Controller实现。

Spring 的优点?

  (1)spring属于低侵入式设计,代码的污染极低;

  (2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;

  (3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。

  (4)spring对于主流的应用框架提供了集成支持。

Spring的AOP理解

  传统的OOP开发的代码逻辑是自上而下,而这自上而下的过程中可能会产生横切性的问题,比如事务管理,日志等,这些是与主逻辑代码关联不是特别大,如果将这些代码放到主逻辑中,会时代吗臃肿和冗余,不利于维护,AOP编程就可以将这些横切性的问题和主业务之间进行分离,从而启动解耦的目的。

  AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

  (1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

  (2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

  ①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例,  生成目标类的代理对象。

  ②如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

spring框架中那些用到了AOP

  拦截器,事务

什么是spring容器

  Spring容器其实是一个Map映射, 里面存储了应用中所有bean的实例, key为该bean实例的Class对象. Spring有两种容器, 分别是 BeanFactory 和 ApplicationContext, 二者的区别在于, BeanFactory采用延迟加载策略, 在第一次调用getBean()时才真正装配该对象. 而 ApplicationContext会在应用启动时就把所有对象一次性全部装配好.

BeanFactory和ApplicationContext有什么区别?

  BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。

(1)BeanFactory:

  是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

    ①继承MessageSource,因此支持国际化。

    ②统一的资源文件访问方式。

    ③提供在监听器中注册bean的事件。

    ④同时加载多个配置文件。

    ⑤载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

(2)

  ①BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

        ②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

        ③相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

(3)

  BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

(4)

  BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

 

 6、请解释Spring Bean的生命周期?

实例化和初始化的区别:实例化是在jvm的堆中创建了这个对象实例,此时它只是一个空的对象,所有的属性为null。而初始化的过程就是讲对象依赖的一些属性进行赋值之后,调用某些方法来开启一些默认加载。比如spring中配置的数据库属性Bean,在初始化的时候就会将这些属性填充,比如driver、jdbcurl等,然后初始化连接

 (1)实例化Bean  

  对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

(2)设置对象属性(依赖注入)

  实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。

(3)处理Aware接口

  接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean,一下是常见的Aware接口,更多接口请自己搜索

  ①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;

  ②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。

  ③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;

(4)BeanPostProcessor

  如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。

(5)InitializingBean 与 init-method:

   如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。如果Bean实现了InitializingBean接口并且调用afterPropertiesSet()方法。

(6)BeanPostProcessor:

   如果想对Bean进行一些实例化后的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessAfterInitialization(Object obj, String s)方法。

(7)DisposableBean

  当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;

(8)destroy-method

  最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

 

对于postProcessBeforeInitialization和postProcessAfterInitialization方法的调用存在一些自己不了解的问题

1、xml配置

<?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 id="test" class="com.zy.Test"></bean>
</beans>

2、Test类实现BeanPostProcessor接口

public class Test implements  BeanPostProcessor{
    public String a;
    public String getA() {
        return a;
    }
    public void setA(String a) {
        this.a = a;
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(((Test1) bean));
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //传入的却是Test1这个对象?
        System.out.println(((Test1) bean));
        return bean;
    }
}

 3、测试发现并没有调用这两个方法。

public class Demo {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        applicationContext.getBean("test");
    }
}

4、继续测试

  添加类(注意这个类不要实现)

package com.zy;
public class Test1{
}

  修改xml

<?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 id="test" class="com.zy.Test"></bean>
    <bean class="com.zy.Test1"></bean>
</beans>

  测试方法不变,发现postProcessAfterInitialization()方法传入的bean竟然是Test1

5、继续测试

  将Test1类实现方法BeanPostProcessor,测试发现 postProcessAfterInitialization()方法有没有被调用.......

6、继续测试,将Test修改为原型就可以触发BeanPostProcessor接口方法了

 

7、spring事件机制(订阅发布模式 == 观察者模式)

  ApplicationContext事件机制是观察者设计模式的 实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。 如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。

  果我们的监听器继承ApplicationListener<ApplicationEvent>;相当于我们这个监听器监听的是Spring里面所有的消息

  两个重要成员

    ApplicationEvent:容器事件,必须由ApplicationContext发布;

    ApplicationListener:监听器,可由容器中的任何监听器Bean担任。

  1. 定义容器事件

package com.cxg.test.springPlatfrom;  
  
import org.springframework.context.ApplicationEvent;  
public class EmailEvent extends ApplicationEvent{  
    private static final long serialVersionUID = 1L;  
    //属性  
    private String address;  
    private String text;  
    //构造方法  
    public EmailEvent(Object source) {  
        super(source);  
    }  
    public EmailEvent(Object source, String address, String text) {  
        super(source);  
        this.address = address;  
        this.text = text;  
    }  
    //getter和setter设置  
    public String getAddress() {  
        return address;  
    }  
    public void setAddress(String address) {  
        this.address = address;  
    }  
    public String getText() {  
        return text;  
    }  
    public void setText(String text) {  
        this.text = text;  
    }  
}  

  2. 定义监听器

package com.cxg.test.springPlatfrom;  
  
import org.springframework.context.ApplicationEvent;  
import org.springframework.context.ApplicationListener;  
/** 
 * Title: email之监听类 
 * 容器事件的监听器类必须实现ApplicationListener接口,实现该接口就必须实现 
 */  
public class EmailNotifier implements ApplicationListener<ApplicationEvent>{ //可以直接写成ApplicationListener<EmailEvent>:表示只监听EmailEvent事件
  
    @Override  
    public void onApplicationEvent(ApplicationEvent event) {  
        if(event instanceof EmailEvent){  
            EmailEvent emailEvent = (EmailEvent) event;  
            System.out.println("email's address:"+emailEvent.getAddress());  
            System.out.println("email's text:"+emailEvent.getText());  
        } else {  
            System.out.println("the Spring's event:"+event);  
        }  
    }  
  
}  

  3. 将监听器注入到spring容器

<bean class="com.cxg.test.springPlatfrom.EmailNotifier" />

  4. 测试

package com.cxg.test.springPlatfrom;  
  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
public class SpringTest {  
    public static void main(String arg[]){  
        //读取Spring容器的配置文件  
        @SuppressWarnings("resource")  
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("application.xml");  
        //创建一个事件对象  
        EmailEvent emailEvent = new EmailEvent("hello Spring!", "cxg@126.com", "This is SpringApplicatoinContext test!");  
        //主动触发事件监视机制  
        applicationContext.publishEvent(emailEvent);  
    }  
}  

应用,监听spring启动完成之后的事件

@Component //applicaiton.xml注意扫描这个包
public class InitAdminListener implements
		ApplicationListener<ContextRefreshedEvent> {

	@Autowired
	private LogininfoService logininfoService;

	/**
	 * 这个方法就是监听到指定的事件之后,要做的事情; ApplicationEvent就是这次监听到的事件对象
	 */
	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		logininfoService.initAdmin();
	}
}

8、 解释Spring支持的几种bean的作用域。

(1)singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。

(2)prototype:为每一个bean请求提供一个实例。

(3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。

(4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。

(5)global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。

9、Spring框架中的单例Beans是线程安全的么?

  Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。

10、Spring如何处理线程并发问题?

  在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。

  ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。

  ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

public class Test {
    static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        //当前线程给ThreadLocal设置值,只能当前线程可以访问
        stringThreadLocal.set("1");
        System.out.println(stringThreadLocal.get());
        new Thread(()->{
            System.out.println(stringThreadLocal.get());
        }).start();
    }
}

  

11、Spring基于xml注入bean的几种方式:

(1)Set方法注入;

(2)构造器注入:①通过index设置参数的位置;②通过type设置参数类型;

(3)静态工厂注入;

(4)实例工厂;

详细:https://blog.csdn.net/a745233700/article/details/89307518

  https://blog.csdn.net/lyt_7cs1dn9/article/details/65631479

12、spring注解有哪些?

参考:https://blog.csdn.net/weixin_39805338/article/details/80770472

13、Spring事务的实现方式和实现原理:

   Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

 

springBoot中 @Component与@Bean的区别

  两者的目的是一样的,都是注册bean到Spring容器中。

  我们写的类,可以用两种方式来注入容器中,当我们引用第三方库中的类需要装配到Spring容器时,则只能通过@Bean来实现

public class WireThirdLibClass {
    @Bean
    public ThirdLibClass getThirdLibClass() {
        return new ThirdLibClass();
    }
}

再举个只能用@Bean的例子:

@Bean
public OneService getService(status) {
    case (status)  {
        when 1:
                return new serviceImpl1();
        when 2:
                return new serviceImpl2();
        when 3:
                return new serviceImpl3();
    }
}

 

  

 

 

 

 

 

 

 

  

posted @ 2019-05-03 23:09  小名的同学  阅读(193)  评论(0编辑  收藏  举报