spring相关问题

1、Spring框架的设计目标,设计理念,和核心是什么

Spring设计目标:Spring为开发者提供一个一站式轻量级应用开发平台;

Spring设计理念:在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持OO(面向对象)设计方法;Spring通过IoC容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给IoC容器,实现解耦;

Spring框架的核心:IoC容器和AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务。

IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件

2、Spring的优缺点是什么?

优点

  • 方便解耦,简化开发
    • Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。
  • AOP编程的支持
    • Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
  • 声明式事务的支持
    • 只需要通过配置就可以完成对事务的管理,而无需手动编程。
  • 方便程序的测试
    • Spring对Junit4支持,可以通过注解方便的测试Spring程序。
  • 方便集成各种优秀框架
    • Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
  • 降低JavaEE API的使用难度
    • Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。

缺点

  • Spring明明一个很轻量级的框架,却给人感觉大而全
  • Spring依赖反射,反射影响性能
  • 使用门槛升高,入门Spring需要较长时间

3、Spring 框架中都用到了哪些设计模式?

  • 单例模式:只有一个共享的实例存在,所有对这个 bean 的请求都会返回唯一的实例。

    Spring中JavaBean默认为单例,因为spring上下文中会有很多个dao\service\action对象,如果用多例模式的话,就每次要用到的时候,都会重新创建一个新的对象,内存中就会有很多重复的对象,所以单例模式的特点就是能减少我们的内存空间,节约性能。
    
    还有常用Spring中 @Repository、@Component、@Configuration @Service注解作用下的类默认都是单例模式的,所以,我目前认为在Spring下使用单例最优的方式是将类@Component注册为组件。使用场景主要有:数据库配置、Redis配置、权限配置、Filter过滤、webMvcConfig、swagger及自定义的时间转换器、类型转换器、对接第三方硬件时,调用硬件的dll、so文件等。
    
    单独使用@Component注解,只能控制到类上,使用@Configuration+@Bean可以控制到方法级别粒度,但是尽量避免@Component+@Bean组合使用,因为@Component+@Bean并不是单例,在调用过程中可能会出现多个Bean实例,导致蜜汁错误。
    并不是所有的注解默认都是单例模式,@RestController就是多例
    
  • 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;

    IOC控制反转也叫依赖注入,它就是典型的工厂模式,通过sessionfactory去注入实例
    
    解释:将对象交给容器管理,你只需要在spring配置文件总配置相应的bean,以及设置相关属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你 需要调用这些bean的类(假设这个类名是A),分配的方法就是调用A的setter方法来注入,而不需要你在A类里面new这些bean了
    
  • 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;

    Spring中的AOP就是典型的代理模式,首先我们先聊聊AOP(面向切面编程)的的一个设计原理:
    
    AOP可以说是对OOP的补充和完善  OOP引入了封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,oop允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能,日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在OOP设计中,它导致大量代码重复,而不利于各个模块的重用。
    
    实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码
    
    简单点解释,比方说你想在你的biz层所有类中都加上一个打印‘你好’的功能,这时就可以用AOP思想来做,你先写个类写个类方法,方法经实现打印‘你好’,然后IOC这个类ref="biz.* "让每个类都注入即可实现。
    
  • 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。

  • 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。

    在Spring中有一个ApplicationListener,采用观察者模式来处理的,ApplicationEventMulticaster作为主题,里面有添加,删除,通知等。
    
    spring有一些内置的事件,当完成某种操作时会发出某些事件动作,他的处理方式也就上面的这种模式
    
  • 适配器模式:将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类能在一起工作,做法是将类自己的接口包裹在一个已存在的类中

    AOP和MVC中,都有用到适配器模式。
    
    spring aop框架对BeforeAdvice、AfterAdvice、ThrowsAdvice三种通知类型的支持实际上是借助适配器模式来实现的,这样的好处是使得框架允许用户向框架中加入自己想要支持的任何一种通知类型,上述三种通知类型是spring aop框架定义的,它们是aop联盟定义的Advice的子类型。
    
    Spring中的AOP中AdvisorAdapter类,它有三个实现:MethodBeforAdviceAdapter、AfterReturnningAdviceAdapter、ThrowsAdviceAdapter。
    
    适配器模式的优缺点
      优点:
    能提高类的透明性和复用性,现有的类会被复用但不需要改变。
    目标类和适配器类解耦,可以提高程序的扩展性。
    在很多业务场景中符合开闭原则。
      缺点:
    在适配器代码编写过程中需要进行全面考虑,可能会增加系统复杂度。
    增加代码阅读难度,过多使用适配器会使系统代码变得凌乱。
    
    springmvc中对handler的处理使用了适配器模式。
    
    springmvc通过HandlerMapping获取到可以处理的handler,这些handler的类型各不相同,对请求的预处理,参数获取都不相同,最简单的做法是根据不同的handler类型,做一个分支处理,不同的handler编写不同的代码。
    
    这样的问题是很明显的,分支判断复杂,代码庞大,不符合单一职责原则。如果要增加一种handler类型,需要修改代码增加分支处理,违反了开闭原则。DispatcherServelt与多个handler发生了交互,违反迪米特法则。
    
    而使用适配器模式,就可以很好的解决这个问题:
    
    不直接对handler进行处理,而是将handler交给适配器HandlerAdapter去处理,这样DispatcherServlet交互的类就只剩下一个接口,HandlerAdapter,符合迪米特法则,尽可能少的与其他类发生交互;
    
    将handler交给HandlerAdapter处理后,不同类型的handler被对应类型的HandlerAdapter处理,每个HandlerAdapter都只完成单一的handler处理,符合单一职责原则;
    
    如果需要新增一个类型的handler,只需要新增对应类型的HandlerAdapter就可以处理,无需修改原有代码,符合开闭原则。
    
    这样,不同的handler的不同处理方式,就在HandlerAdapter中得到了适配,对于DispatcherServlet来将,只需要统一的调用HandlerAdapter的handle()方法就可以了,无需关注不同handler的处理细节。
    

4、什么是自动装配

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 redis 的话,直接在项目中引入对应的 starter 即可。

5、springBoot如何实现自动装配

Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖

先看@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制

  • @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类

  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilterAutoConfigurationExcludeFilter

@EnableAutoConfiguration 是实现自动装配的重要注解,我们以这个注解入手。

@EnableAutoConfiguration:实现自动装配的核心注解

@EnableAutoConfiguration 只是一个简单地注解,自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector类。

AutoConfigurationImportSelector:加载自动装配类

AutoConfigurationImportSelector类的继承体系如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

}

public interface DeferredImportSelector extends ImportSelector {

}

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

可以看出,AutoConfigurationImportSelector 类实现了 ImportSelector接口,也就实现了这个接口中的 selectImports方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。

private static final String[] NO_IMPORTS = new String[0];

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
     // <1>.判断自动装配开关是否打开
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    //<2>.获取所有需要装配的bean
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                                                                              annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

这里我们需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的。

private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

protected AutoConfigurationEntry getAutoConfigurationEntry(
    AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

第 1 步:

isEnabled(annotationMetadata)判断自动装配开关是否打开。默认spring.boot.enableautoconfiguration=true,可在 application.propertiesapplication.yml 中设置

protected boolean isEnabled(AnnotationMetadata metadata) {
    if (getClass() == AutoConfigurationImportSelector.class) {
        return getEnvironment().getProperty(
            EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
            true);
    }
    return true;
}

第 2 步

getAttributes(annotationMetadata)用于获取EnableAutoConfigurtion注解中的 excludeexcludeName

attributes对象结果如下:

{excludeName=[], exclude=[com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure]}

第 3 步

获取需要自动装配的所有配置类,读取META-INF/spring.factories,结果存储到List<String> configurations中。

不光是这个依赖下的META-INF/spring.factories被读取到,所有 Spring Boot Starter 下的META-INF/spring.factories都会被读取到。

第 4 步

移除排除的类configurations.removeAll(exclusions);

configurations = filter(configurations, autoConfigurationMetadata);经历了一遍筛选,保留@ConditionalOnXXX 中的所有条件都满足的类。

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
  • @ConditionalOnJava:基于 Java 版本作为判断条件
  • @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
  • @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下

总结

Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖

6、spring注入方式

  • 属性注入:

    public class UserServiceImpl implents UserService{
         @Autowire
         private UserDao userDao;
     }
    
  • set方法注入

    public class UserServiceImpl implents UserService{
         private UserDao userDao;
         
         @Autowire
         public serUserDao(UserDao userDao){
             this.userDao = userDao;
         }
     }
    
  • 构造器注入

    public class UserServiceImpl implents UserService{
        private UserDao userDao;
        
        @Autowire
        public UserServiceImpl(UserDao userDao){
            this.userDao = userDao;
        }
    }
    

    基于字段的依赖注入缺点

    1. 对于有final修饰的变量不好使

    Spring的IOC对待属性的注入使用的是set形式,但是final类型的变量在调用class的构造函数的这个过程当中就得初始化完成,这个是基于字段的依赖注入做不到的地方.只能使用基于构造函数的依赖注入的方式

    1. 掩盖单一职责的设计思想

    我们都知道在OOP的设计当中有一个单一职责思想,如果你采用的是基于构造函数的依赖注入的方式来使用Spring的IOC的时候,当你注入的太多的时候,这个构造方法的参数就会很庞大,类似于下面.

    当你看到这个类的构造方法那么多参数的时候,你自然而然的会想一下:这个类是不是违反了单一职责思想?.但是使用基于字段的依赖注入不会让你察觉,你会很沉浸在@Autowire当中

    1. 与Spring的IOC机制紧密耦合

    当你使用基于字段的依赖注入方式的时候,确实可以省略构造方法和setter这些个模板类型的方法,但是,你把控制权全给Spring的IOC了,别的类想重新设置下你的某个注入属性,没法处理(当然反射可以做到).

    本身Spring的目的就是解藕和依赖反转,结果通过再次与类注入器(在本例中为Spring)耦合,失去了通过自动装配类字段而实现的对类的解耦,从而使类在Spring容器之外无效.

    1. 隐藏依赖性

    当你使用Spring的IOC的时候,被注入的类应当使用一些public类型(构造方法,和setter类型方法)的方法来向外界表达:我需要什么依赖.但是基于字段的依赖注入的方式,基本都是private形式的,private把属性都给封印到class当中了.

    1. 无法对注入的属性进行安检

    基于字段的依赖注入方式,你在程序启动的时候无法拿到这个类,只有在真正的业务使用的时候才会拿到,一般情况下,这个注入的都是非null的,万一要是null怎么办,在业务处理的时候错误才爆出来,时间有点晚了,如果在启动的时候就暴露出来,那么bug就可以很快得到修复(当然你可以加注解校验).

    如果你想在属性注入的时候,想根据这个注入的对象操作点东西,你无法办到.我碰到过的例子:一些配置信息啊,有些人总是会配错误,等到了自己测试业务阶段才知道配错了,例如线程初始个数不小心配置成了3000,机器真的是狂叫啊!这个时候就需要再某些Value注入的时候做一个检测机制.

7、spring创建bean流程

  1. 利用该类的构造方法来实例化得到一个对象(但是如果一个类中有多个构造方法, Spring则会进行选择,这个叫做推断构造方法)

  2. 得到一个对象后,Spring会判断该对象中是否存在被@Autowired注解了的属性,把这些属性找出来并由Spring进行赋值(依赖注入)

  3. 依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、 BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须实现该接口中所定义的setBeanName()setBeanClassLoader()setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数

  4. Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解了,如果存在,Spring会调用当前对象的此方法(初始化前。

  5. 紧接着,Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就 表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当前对象中的afterPropertiesSet()方法(初始化)

  6. 最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完 了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化后)

    原文链接:https://www.jianshu.com/p/57ed586e258c

8、spring bean的作用域

1、singleton:唯一bean实例,Spring中的bean默认都是单例的。

​ spring默认使用单例模式,因为Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。而singleton作用域的Bean实例一旦创建成功,可以重复使用。

2、prototype:每次请求都会创建一个新的bean实例。

3、request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTPrequest内有效。

4、session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTPsession内有效。

5、global-session:全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值