【面试】--【Spring】

IOC:控制反转

依赖注入(DI) 假如A类中有个属性是B类,那么将所B类通过setter,构造函数等方式注入到A类中去。而不是通过new对象的形式。

例如 轮子 <-依赖<-底盘 <-依赖<-箱体<-依赖<-行李箱. 这种上层依赖下层的模式,假如轮子的构造函数变了,那么底盘,箱体,行李箱的构造函数也要改

依赖注入  行李箱<-注入<-箱体<-注入<-底盘<-注入<-轮子,这种注入的方式,假如轮子的构造函数变了,只需要改轮子即可,而底盘,箱体,行李箱的构造函数不需要变。

这种方式也可以方便协同开发。

DI的方式

setter

interface 实现特定接口

constructor

annotation 基于注解  autowire

依赖倒置原则(DIP):提供了一种思想,高层模块不应依赖于低层模块

控制反转(IOC):通过(依赖注入)DI的方法实现

第三方容器:IOC Container(控制反转容器)

IOC容器的优势

避免在各处使用new来创建类,并且可以做到统一维护

创建实例时不需要了解其中细节

我们手动创建一个行李箱的过程:new轮子 -->  new底盘 -->  new箱体 -->  new行李箱.    是从底层往上层new,需要了解底层的

创建细节

IOC创建行李箱的过程:行李箱->查找config->箱体->查找config->底盘->查找config->轮子,查完后再开始创建,-->

new轮子 -->  new底盘 -->  new箱体 -->  new行李箱。IOC会隐藏中间的创建细节

 

IOC容器的执行原理

  1. 读取Bean配置信息(XML<bean> ,有@Configuration注解的java类配合Bean标签返回bean,注解@Autowired,给实体类加Component,application接口加ComponentScan)
  2. 在spring容器中生成一份Bean定义注册表,根据Bean定义注册表实例化Bean,{Bean1,Bean2,Bean3}
  3. 将Bean实例放到Spring容器,装配好Bean之间的依赖关系,放到Bean缓存池。

Spring IOC支持的功能

依赖注入 依赖检查 自动装配 支持集合 指定初始化方法和销毁方法 支持回调方法

Spring是如何设计容器的

Spring IOC容器的核心接口

BeanFactory

AppliacationContext

<!--配置事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"/>
</bean>

所有的bean配置及注解会被解析成BeanDefinition,

通过接口BeanDefinitionRegistry中的registerBeanDefinition方法 放到一个ConcurentHashMap中去,键为bean的name,值为

BeanDefinition。

同时还将bean的name存到beanDefinitionNames(一个arraylist)中去,方便实例化。

BeanFactory

提供了IOC的配置机制

包含Bean的各种定义,便于实例化Bean

建立Bean之间的依赖关系

Bean生命周期的控制

BeanFactory中的方法

多个getBean方法:可以按名称,按类型来获取Bean

isSingleton方法:Bean是否为单例,Spring容器中Bean默认是单例。

BeanFactory和AppliacationContext的比较

AppliacationContext是BeanFactory的子接口之一,相比BeanFactory多了更多的功能

BeanFactory是spring框架的基础设施,面向Spring。(汽车的发动机)

AppliacationContext面向使用Spring框架的开发者(汽车)

AppliacationContext的功能

实现了BeanFactory :能够管理、装配Bean

ResourcePatternResolver:能够加载资源文件

MessageSource:能够实现国际化功能

ApplicationEventPublisher:能够注册监听器,实现监听机制

//将一个普通的person类通过配置类装配到ioc中
@Configuration
public class ApplicationConfig {
    @Bean(name="person")
    public Person initPerson(){
        Person user = new Person();
        user.setId(1L);
        user.setName("Jack");
        return user;
    }
}
//使用注解的方式装载到ioc容器中
@Component("student")
public class Student {
    @Value("2")
    private long id;
    @Value("Tom")
    private String name;
}
public class Person {
    private long id;
    private String name;
    //通过注解实现依赖注入
    @Autowired
    private Pet pet;

}


//假如pet是个接口有多个实现类(dog,bird),那么注入时需要通过@Qualifier指定是哪一个实现类
public class Person {
    private long id;
    private String name;

    @Qualifier("bird")
    @Autowired
    private Pet pet;
}

getBean方法的代码逻辑

  1. 转换BeanName
  2. 从缓存中加载实例
  3. 实例化Bean
  4. 检测parentBeanFactory
  5. 初始化依赖的Bean
  6. 创建Bean

一个 Bean 加载会经历这么几个阶段

Spring 的工作流进行一个粗略的概括,主要为两大环节:

  • 解析,读 xml 配置,扫描类文件,从配置或者注解中获取 Bean 的定义信息,注册一些扩展功能。
  • 加载,通过解析完的定义信息获取 Bean 实例。

一个 Bean 加载会经历这么几个阶段:

  • 获取 BeanName,对传入的 name 进行解析,转化为可以从 Map 中获取到 BeanDefinition 的 bean name。
  • 合并 Bean 定义,对父类的定义进行合并和覆盖,如果父类还有父类,会进行递归合并,以获取完整的 Bean 定义信息。
  • 实例化,使用构造或者工厂方法创建 Bean 实例。
  • 属性填充,寻找并且注入依赖,依赖的 Bean 还会递归调用 getBean方法获取。
  • 初始化,调用自定义的初始化方法。
  • 获取最终的 Bean,如果是 FactoryBean 需要调用 getObject 方法,如果需要类型转换调用 TypeConverter 进行转化。

SpringBean的作用域

singleton:Spring的默认作用域,容器里拥有唯一的Bean实例

prototype:针对每个getBean请求,容器都会创建一个Bean实例。(会频繁的创建和销毁,额外开销)

web容器则支持其他三种

request:会为每个Http请求创建一个Bean实例

session:会为每个session创建一个Bean实例

globalSession:会为一个全局的Http session创建一个Bean实例,该作用域仅对Portlet有效

SpringBean的生命周期

有销毁的destory()方法

SpringAOP

面向切面编程

通用化功能代码的实现,对应的是所谓的切面

业务功能代码和切面代码分开后,架构变得高内聚低耦合

确保功能的完整性 ,将切面代码织入

AOP的三种织入方式

编译时织入:需特殊的java编译器,如AspectJ

类加载时织入:需特殊的java编译器,如AspectJ和AspectWerkz

运行时织入:Spring采用的方式,通过动态代理的方式,实现简单,会有运行时的开销,不需要特殊的编译器。

AOP主要名词

Aspect:通用功能的代码实现

Target:被织入Aspect的对象

Join Point:可以作为切入点的机会,所有方法都可以作为切入点

Pointcut:Aspect实际被应用在的join Point,支持正则

Advice:类里的方法以及这个方法如何织入到目标方法的方式

Weaving(织入):AOP的实现过程

@Aspect
@Component
public class RequestLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class);
    //切点
    @Pointcut("execution(public * com.studyjava.framework.ioc.web..*.*(..))")
    public void  webLog(){}

    //方法执行前记录url和IP
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint){
        //接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录下请求内容
        logger.info("URL:"+request.getRequestURL().toString());
        logger.info("IP: "+request.getRemoteAddr());
    }

    //方法返回后,打印返回值
    @AfterReturning(returning = "ret",pointcut = "webLog()")
    public void doAfterReturnning(Object ret){
        //处理完请求,返回内容
        logger.info("Response: "+ret);
    }
}

AOP通知类型

前置通知(Before)

后置通知(AfterReturning)

异常通知(AfterThrowing)

最终通知(After)

环绕通知(Around)

SpringAOP原理

jdkProxy和Cglib两种方式

默认策略:如果目标类是接口,则用JDK动态代理实现,否则用后者

JDKProxy的核心:InvocationHandler接口和Proxy类;通过反射接收被代理的类,并且要求被代理的类实现了InvocationHandler接口。

Cglib:以继承的方式动态生成目标类的代理。动态生成某个类的子类,通过修改字节码的方式(ASM)实现,如果某个类声明为final,则无法使用cglib

反射机制在生成类的过程中比较高效;ASM在生成类之后的执行过程比较高效

代理模式

代理模式:接口+真实实现类+代理类

将真实实现类作为代理类构造方法的参数传入代理类,让代理类来完成相关操作

Spring里代理模式的实现

真实实现类的逻辑包含在里getBean()方法里

getBean()方法返回的实际上是Proxy的实例(所以springAOP对于不在IOC容器的bean是无能为力的)

Proxy实例是Spring采用JDK Proxy或CGlib动态生成的

Spring怎么解决循环依赖?

首先明白两点:

  • Spring是通过递归的方式获取目标bean及其所依赖的bean的;
  • Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。

结合这两点,也就是说,Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的。

三级缓存分别指:

singletonObjects:单例对象的cache

earlySingletonObjects :提前暴光的单例对象的Cache

singletonFactories :单例对象工厂的cache

让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。


循环依赖就说明程序设计有问题,这就是springboot推荐使用构造注入的原因,使用构造器注入如果产生循环依赖直接报错无法启动,而setter注入会成功。注解autowired其实底层也是setter注入

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

工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
单例模式:Bean默认为单例模式。
代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被自动更新,如Spring中listener的实现–ApplicationListener。

@Component, @Controller, @Repository, @Service 有何区别?

@Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。

@Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。

@Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。

@Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。

@Autowired和@Resource之间的区别

@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。

@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

@Qualifier 注解有什么作用

当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,您可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。

@RequestMapping 注解有什么用?

@RequestMapping 注解用于将特定 HTTP 请求方法映射到将处理相应请求的控制器中的特定类/方法。此注释可应用于两个级别:

Spring支持的事务管理类型, spring 事务实现方式有哪些?

Spring支持两种类型的事务管理:

编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。

声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。

说一下Spring的事务传播行为

spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。

① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

<!-- 事务管理器,依赖于数据源 -->
    <bean id="txManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 编写通知:对事务进行增强(通知),需要编写切入点和具体执行事务的细节 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <!-- 给切入点方法添加事务详情,name表示方法名称,*表示任意方法名称,propagation用于设置传播行为,read-only表示隔离级别,是否只读 -->
            <tx:method name="find*" propagation="SUPPORTS"
                rollback-for="Exception" />
            <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"
                read-only="false" />
        </tx:attributes>
    </tx:advice>
    <!-- aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 -->
    <aop:config>
        <!-- 切入点 -->
        <aop:pointcut expression="execution(* com.mengma.service.*.*(..))"
            id="txPointCut" />
        <!-- 切面:将切入点与通知整合 -->
        <aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" />
    </aop:config>


 

说一下 spring 的事务隔离?

spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:

ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;

ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);

ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;

ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;

ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。

 

 

 

 

Spring由哪些模块组成?

Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、 Web、 消息(Messaging) 、 Test等 6 个模块中。

spring core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
spring beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。
spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。
spring jdbc:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。
spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。
spring test:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。
 

Spring 应用程序有哪些不同组件?

接口 - 定义功能。
Bean 类 - 它包含属性,setter 和 getter 方法,函数等。
Bean 配置文件 - 包含类的信息以及如何配置它们。
Spring 面向切面编程(AOP) - 提供面向切面编程的功能。
用户程序 - 它使用接口。

BeanFactory 和 ApplicationContext有什么区别?

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

依赖关系

BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。

ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

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

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

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

同时加载多个配置文件。

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

加载方式

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

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

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

创建方式

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

注册方式

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

有哪些不同类型的依赖注入实现方式?

依赖注入是时下最流行的IoC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection)和构造器注入(Constructor Injection)三种方式。其中接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。

构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。

Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。

构造器依赖注入和 Setter方法注入的区别

构造函数注入                               setter 注入
没有部分注入                               有部分注入
不会覆盖 setter 属性                   会覆盖 setter 属性
任意修改都会创建一个新实例    任意修改不会创建一个新实例
适用于设置很多属性                   适用于设置少量属性
两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。

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

  1. Set方法注入;

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

  3. 静态工厂注入;

  4. 实例工厂;

你怎样定义类的作用域?

当定义一个 在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope 属性 必须设为 singleton。

Spring框架支持以下五种bean的作用域:

singleton : bean在每个Spring ioc 容器中只有一个实例。
prototype:一个bean的定义可以有多个实例。
request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
注意: 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。

Spring框架中的单例bean是线程安全的吗?

不是,Spring框架中的单例bean不是线程安全的。

spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。

实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。

有状态就是有数据存储功能。
无状态就是不会保存数据。

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

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

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

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

使用@Autowired注解自动装配的过程是怎样的?

使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。

在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;

如果查询的结果不止一个,那么@Autowired会根据名称来查找;

如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

自动装配有哪些局限性?

自动装配的局限性是:

重写:你仍需用 和 配置来定义依赖,意味着总要重写自动装配。

基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。

模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。

你可以在Spring中注入一个null 和一个空字符串吗?

可以。

怎样开启注解装配?

注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置 <context:annotation-config/>元素。

 

@Required 注解有什么作用

这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。示例:

public class Employee {
    private String name;
    @Required
    public void setName(String name){
        this.name=name;
    }
    public string getName(){
        return name;
    }
}

@Autowired 注解有什么作用

@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。

public class Employee {
    private String name;
    @Autowired
    public void setName(String name) {
        this.name=name;
    }
    public string getName(){
        return name;
    }
}
 


 

你更倾向用那种事务管理类型?

大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

 

Spring面向切面编程(AOP)(13)

什么是AOP

OOP(Object-Oriented Programming)面向对象编程,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。

AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?

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

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

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

JDK动态代理和CGLIB动态代理的区别

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

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

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

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

InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

 

如何理解 Spring 中的代理?

将 Advice 应用于目标对象后创建的对象称为代理。在客户端对象的情况下,目标对象和代理对象是相同的。

Advice + Target Object = Proxy

解释一下Spring AOP里面的几个名词

(1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。

(2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

(3)通知(Advice):在AOP术语中,切面的工作被称为通知。

(4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

(5)引入(Introduction):引入允许我们向现有类添加新方法或属性。

(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

(7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:

编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。
Spring在运行时通知对象

通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。

直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。

Spring只支持方法级别的连接点

因为Spring基于动态代理,所以Spring只支持方法连接点。Spring缺少对字段连接点的支持,而且它不支持构造器连接点。方法之外的连接点拦截功能,我们可以利用Aspect来补充。

在Spring AOP 中,关注点和横切关注的区别是什么?在 spring aop 中 concern 和 cross-cutting concern 的不同之处

关注点(concern)是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。

横切关注点(cross-cutting concern)是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

Spring通知有哪些类型?

在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。

Spring切面可以应用5种类型的通知:

前置通知(Before):在目标方法被调用之前调用通知功能;
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
返回通知(After-returning ):在目标方法成功执行之后调用通知;
异常通知(After-throwing):在目标方法抛出异常后调用通知;
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
同一个aspect,不同advice的执行顺序:
①没有异常情况下的执行顺序:
around before advice
before advice
target method 执行
around after advice
after advice
afterReturning
②有异常情况下的执行顺序:
around before advice
before advice
target method 执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生


什么是切面 Aspect?

aspect 由 pointcount 和 advice 组成,切面是通知和切点的结合。 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑编织到切面所指定的连接点中.
AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:

如何通过 pointcut 和 advice 定位到特定的 joinpoint 上
如何在 advice 中编写切面代码.
可以简单地认为, 使用 @Aspect 注解的类就是切面.

解释基于XML Schema方式的切面实现

在这种情况下,切面由常规类以及基于XML的配置实现。

解释基于注解的切面实现

在这种情况下(基于@AspectJ的实现),涉及到的切面声明的风格与带有java5标注的普通java类一致。

有几种不同类型的自动代理?

BeanNameAutoProxyCreator

DefaultAdvisorAutoProxyCreator

Metadata autoproxying

 

spring管理bean原理

1、读取config.xml文件的bean标签放入数组,读取内容包括id和class。 
2、循环数组并根据class路径利用反射机制实例化Bean,并放入Map。
3、根据传入的BeanId获取Map中对应的bean实例。

 

Spring-bean的循环依赖以及解决方式

Spring中循环依赖场景有:

(1)构造器的循环依赖

(2)field属性的循环依赖。

Spring的单例对象的初始化主要分为三步:

(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象

(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

(3)initializeBean:调用spring xml中的init 方法。

让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

Spring 事务管理有两种方式:声明式事务和编程式事务。其中,编程式事务是基于TransactionTemplate的手动管理,不太常用,我们常用的是声明式事务。声明式事务又有三种实现方式:

基于TransactionProxyFactoryBean的方式

基于AspectJ的XML方式

基于注解的方式

一、基于注解的方式

(一)在Spring的xml文件中的配置

 1. 配置事务管理器

 2. 开启事务注解

在需要开启事务管理的方法上加上注解@Transactional。

在@Transactional 注解中可以进行如下属性设置:

propagation:指定事务的传播行为(即当前的事务方法被另外一个事务方法调用时,如何使用事务, 默认取值为 REQUIRED,

即使用调用方法的事务)。

isolation:指定事务的隔离级别,最常用的取值READ_COMMITTED。

默认情况下Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下取默认值即可。

readOnly:指定事务是否为只读,表示这个事务只读取数据但不更新数据。这样可以帮助数据库引擎优化事务.。若真的是一个只读取数据库值的方法,应设置 readOnly=true。

timeout:指定强制回滚之前事务可以占用的时间。若超过了timeout指定的时间还为执行,则会抛出异常。

4. xml配置文件(applicationContext-tx.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"

       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"

       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.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--开启注解扫描-->

    <context:component-scan base-package="com.zm"></context:component-scan>

    <!--导入资源文件-->

    <context:property-placeholder location="classpath:db.properties"/>

    <!--配置druid数据源-->

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">

        <property name="driverClassName" value="${jdbc.driverClassName}"/>

        <property name="url" value="${jdbc.url}"/>

        <property name="username" value="${jdbc.username}"/>

        <property name="password" value="${jdbc.password}"/>

        <property name="initialSize" value="${jdbc.initialSize}"/>

        <property name="minIdle" value="${jdbc.minIdle}"/>

        <property name="maxActive" value="${jdbc.maxActive}"/>

    </bean>

    <!--配置spring的JdbcTemplate-->

    <bean id="template" class="org.springframework.jdbc.core.JdbcTemplate">

        <!--引入数据源-->

        <property name="dataSource" ref="dataSource"/>

    </bean>

    <!--配置事务管理器-->

   <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">

       <!--使用哪个数据源-->

       <property name="dataSource" ref="dataSource"/>

   </bean>

   <!--启用事务注解-->

    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

</beans>

2、基于XML文件的配置方式

(一)xml文件中的配置方式

也可以在<tx:method name="" /> 标签中设置事务的传播方式,隔离级别。是否可读等属性。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值