spring

目录

1.what is spring?

2.咩系ioc?

3.Spring创建单例Bean的大概过程

4.Spring如何解决循环依赖?

5.单例bean是线程安全的吗?

6.spring声明式事务

7.编程式事务

8.啥是AOP? 

9.关于AspectJ

10. AspectJ 的通知类型

11.AspectJ 的切入点表达式 

12.AOP的用法


1.what is spring?

spring是一个轻量级的开源框架。

spring的核心概念是ioc和aop。spring可以理解成是一个bean容器,ioc控制反转帮我们完成bean的创建和处理bean之间的依赖关系。引入对象不再是强依赖,将代码解耦合。aop面向切面编程,可以在不改变原业务代码的基础上,对方法进行功能增强,有利于方法间的松耦合。还能减少代码的冗余。

spring集成其他框架的能力很强,也很简单。spring就像一个排插,其他框架就像一个插头,只需要简单的配置就能集成其他框架。

2.咩系ioc?

ioc全名为inversion of control顾名思义控制反转。将创建对象的控制权交给spring容器。何为正转?正转就是开发人员在代码中手动用new创建的对象。这样创建对象代码写死在程序中,耦合性极强,不利于维护和拓展,因此出现了ioc的概念。将对象交给spring容器,让spring管理对象的创建,对象的属性注入,对象的生命周期等,开发人员只需要在需要的时候注入所需对象,使用即可。spring为我们创建对象首先会调用构造方法(不一定是无参构造)创建对象,然后进行属性赋值。属性赋值也称依赖注入,DI(Dependency Injection)。di是ioc的核心功能。

3.Spring创建单例Bean的大概过程

userService->1.推断构造方法->2.依赖注入->3.初始化前(@PostConstruct)->4.初始化(InitializingBean接口方法)->5.初始化后(Aop)->6.放入单例池Map<beanName,Bean>

推断构造方法:

1.当在无参构造方法存在的时候默认使用无参构造创建对象。不管有没有有参构造。

2.当没有无参构造方法时,只有一个有参构造,使用有参构造创建对象。

3.当没有无参构造方法时,有多个有参构造,spring就不知道用哪个构造方法了。

4.当没有无参构造方法时,有多个有参构造,我们在某个有参构造上加入@Autowried注解,spring就知道他要使用这个有参构造来创建对象,并且自动给形参注入对象先byType再byName,注入失败就会报错。

依赖注入:

1.根据反射机制判断属性上是否有@Autowired注解。

2.如果有,先byType再byName注入对象。 

初始化前 初始化:

1.假设我们有这样一个需求,在我们UserService类中有一个User user属性,需要操作数据库之后将数据库返回的记录封装后赋值给user。这样的话我们需要再依赖注入后执行赋值操作。因为注入UserMapper后才能操作数据库。我们只需要在类的方法上加上@PostConstruct注解或者实现InitializingBean接口的方法在方法上完成赋值操作即可。

2.“初始化前”和“初始化”可以实现这类需求。

初始化后:

1.初始化后主要是使用aop创建代理对象。

2.在类中定义了切入点后,spring会在创建该类的bean时,使用jdk动态代理或者cglib动态代理为该类创建代理类对象。

3.代理类的伪代码

  jdk动态代理

  UserServiceProxy extends Proxy implements UserService{

     void test(){

        //@Before等等

              this.h.invocationHandler.invoke();

  }

}

  

cglib动态代理

  UserServiceProxy  extends UserService{

     UserService targer;

    @Override

     void test(){

        //@Before等等

           target.test();

  }

}

target对象是在“初始化后”步骤之前创建的对象。称为普通对象。

代理后的对象称为代理对象。

4.spring开启事务也是在这个步骤通过动态代理完成。

 放入单例池:

1.放入单例池中的对象是代理对象或者是普通对象(不需要进行aop的类的对象)。

2.单例池也称为一级缓存。

4.Spring如何解决循环依赖?

假设AService创建bean时要注入BService类的属性。BService创建bean时要注入AService类的属性。

spring使用三级缓存来解决循环依赖。

假设我们先创建AServie的bean。在creatignSet中记录AService正在创建bean。在使用构造方法创建出普通对象之后,将AService的beanName,普通对象和一些信息构造成一个可执行的lambda表达式放入三级缓存singletonFactories。然后进行依赖注入,此时去单例池SingletonObjects中找BService的bean,没找到,去二级缓存earlySingletonObjects中找,没找到BService的bean,然后去三级缓存singletonFactories找,没找到BService的lambda表达式。所以转头去创建BService的bean。

创建BService的bean,在creatingSet中标记BService正在创建。根据构造方法创建BService的普通对象。然后进行依赖注入,去一级缓存(单例池)SingletonObjects中找,没找到。然后发现AService在creatingSet中有标记,所以此时就出现了循环依赖!!!去二级缓存中找,也没找到。去三级缓存中找,找到了。执行对应的lambda表达式可以获得一个临时的AService对象。众所周知我们在注入对象的时候如果对象是需要aop的我们要注入代理对象,不注入代理对象功能会缺失!执行lambda表达式后我们可以获得代理对象或者普通对象。这取决于被注入的类需不需要代理。将获得的对象注入并且放入二级缓存earlySingletonObjects。至此BService就可以继续执行创建bean的其他过程了,循环依赖被打破。BService的bean创建完后放入单例池中,从creatingSet移除标记。

创建玩BSevice的bean对象后,我们的AService就能注入BService了。然后进行bean创建其他的过程,因为AService在之前已经提前aop了所以这里就不用再aop了。创建完AService的bean后放入单例池。至此完成两个bean的创建。

总结:

1.三级缓存singlrtonFactories其实是真正打破循环依赖的一级。在执行完构造方法后无脑将普通对象的lambda表达式放入三级缓存,以备有时只需。lambda表达式被执行一次之后会自动被移除出三级缓存。

2.二级缓存存的是执行lambda表达式后创建的临时对象,对象里面的属性是没有赋值的。只是为了让我们注入而已。二级缓存在一对一的时候展现不出来效果,假如AService需要注入BService,CService。BService,CService又同时需要注入AService。假设先创建A后B后C,我们上面创建完BService后,CService就不需要经过这么多过程了,直接在二级缓存中就找到了AService的对象。

3.一级缓存存的是最终的bean。需要代理的就是代理对象,不需要代理的就是原对象。

4.三级缓存是中的每一级都不可缺少。这就是spring容器的巧妙之处。

5.没有无参构造方法可能会导致处理不了循环依赖!

5.单例bean是线程安全的吗?

spring中的单例bean可以是安全的。spring并没有为单例bena做任何解决线程安全问题的处理。但为什么单例bean确实安全的呢?首先构成线程不安全有三个条件,多线程并发,多线程共享某一资源,资源会被修改。由此看来单例bean符合前两条,只要我们不符合第三条就不构成安全问题。

对象分为有状态对象和无状态对象。有状态就是可以存储数据,里面的数据会改变。无状态就是不存储数据。

我们的controller,service,dao对象在大部分情况下都是无状态对象,所以原则上不存在安全问题。大部分情况下我们只会在方法中创建有状态对象,这些对象在每个线程内都是独立存在的互不影响,所以也不会构成线程安全问题。所以spring提倡的开发模式,有利于我们避免线程安全问题。

dao操作数据库的connection是有状态对的。spring事务管理器使用Threadlocal为不同线程创建多个connection副本。不同线程使用自己的connection,不同线程之间互不影响。如果单例bean必须存在线程安全问题,被多个线程修改,那么就只能使用线程同步方法了。

一般情况下不要在bean中声明有状态的实例变量或类变量。

6.spring声明式事务

1.事务是数据库的概念,spring将事务的概念用在service层,因为service层操作调用dao方法操作数据库。

2.在没有使用spring之前,我们在需要使用事务之前要了解多种数据库访问技术对事务的操作,对开发人员来说比较困难。spring事务为我们统一spring操作事务的方法。spring中事务管理器PlatFormTransactionManager接口为我们定义了操作数据库的基本方法,不同数据库访问技术需要实现这个接口来完成自己的事务处理。例如:DateSourceTransactionManager是mybatis事务管理器。创建对应的事务管理器对象spring就能知道你要用那种数据库访问技术的事务处理机制。

3.有了事务管理器,我们只需要在service方法上加入@Transactional注解声明事务。

4.spring事务的传播行为propagation。传播行为是作用在被调用的方法上的。

PROPAGATION_REQUIRED:默认的传播行为。需要事务。若当前存在一个事务,则加入当前事务。若当前没有事务,则开启一个事务。

PROPAGATION_SUPPORTS:支持事务。若当前存在一个事务,则加入当前事务。若当前没有事务,就不用事务。

PROPAGATION_REQUIRES_NEW:需要新事务。若当前存在一个事务,则当前事务挂起,开启一个新的事务。若当前不存在事务,开启一个事务。

PROPAGATION_MANDATORY:一定要事务。若当前有事务,则加入当前事务。若当前没有事务,则抛出异常。

PROPAGATION_NOT_SUPPORTED:不支持事务。若当前有事务,则挂起当前事务。

PROPAGATION_NEVER:不支持事务。若当前有事务,则抛出异常。

PROPAGATION_NESTED:嵌套事务。若当前存在父事务,则开启一个新的子事务。若当前不存在事务,开启一个新的事务。父事务回滚,子事务必定要回滚。

5.事务的隔离级别

ISOLATION_DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。

ISOLATION_READ_UNCOMMITTED:读未提交。未解决任何并发问题。

ISOLATION_READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。

ISOLATION_REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读

ISOLATION_SERIALIZABLE:串行化。不存在并发问题。

6.事务的超时时间一般不设置,因为很多因素不确定。

7.spring如何实现声明式事务的呢?

使用动态代理机制。在创建bean的初始化后阶段,spring扫描方法上是否有@Transactional注解,如果有,则使用aop(jdk动态代理或cglib动态代理)创建代理对象。

代理类的伪代码:

UserServiceProxy extends Proxy implements UserService{

   /..../

   void aa(){

      //设置不自动提交事务;

      this.h.invocationHandler.invoke();

      //提交事务/异常回滚

  }

}

UserServiceProxy extends UserService{

   UserService target;

  @Override 

  void aa(){

      //设置不自动提交事务;

       target.aa();

      //提交事务/异常回滚

  }

}

8.spring声明式事务失效的可能原因**********************

8.1 表的存储引擎不支持事务。mysql中InnoDB存储引擎是支持事务的。

8.2 Bean没有被spring管理。没有在业务类上加入@Service注解。

8.3 方法不是public修饰的。要使用@Transactional注解声明事务,被声明的方法必须是public修饰的。

8.4 方法被final修饰。final方法不能被cglib动态代理但可以JDK动态代理。

8.5 没有创建事务管理器对象。(springboot自动帮我们配置事务DataSourceTransactionManagerAutoConfiguration类,已经默默地帮你开启了事务。)

8.6 调用自身有事务方法。解决办法:自己注入自己。自己注入自己就是注入单例池中的代理类对象。

8.7 方法本身不支持事务。如PROPAGATION_NEVER。

8.8 异常被catch掉了。如抛出的RuntimeException类及其子类被catch了,那就不会回滚。

8.9 抛出的异常不是RuntimeException类及其子类或ERROR,而且没有在rollbackfor处声明。

8.10 在事务方法中开启另一个线程调用事务方法。在spring事务中,一个事务代表一个连接对象,而不同线程用的连接对象是不一样的。所以事务当然也不会一样。被调用的方法回滚不会影响到原方法。

7.编程式事务

Spring官方推荐使用TransactionTemplate方式,使用时直接注入即可。

8.啥是AOP? 

 AOP:Aspect Orient Programming面向切面编程。面向切面编程就是在不影响原来业务方法的基础上,将切面方法织入业务方法上,而不用修改业务方法的代码。例如安全检查,事务,日志等操作。这样操作可能用于多个业务方法,使用aop织入方法的好处是,可以减少代码的冗余,和业务之间的解耦,提高代码的可重用性,提高开发效率。

9.关于AspectJ

AspectJ是AOP思想的一种实现,不是spring团队开发的。AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。通常spring aop的开发都是用AspectJ实现。

10. AspectJ 的通知类型

(1)前置通知 (2)后置通知 (3)环绕通知 (4)异常通知 (5)最终通知

11.AspectJ 的切入点表达式 

AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

解释:

modifiers-pattern] 访问权限类型

ret-type-pattern 返回值类型

declaring-type-pattern 包名类名

name-pattern(param-pattern) 方法名(参数类型和参数个数)

throws-pattern 抛出异常类型

?表示可选的部分

以上表达式共 4 个部分。 execution(访问权限 方法返回值 方法声明(参数) 异常类型)

12.AOP的用法

1.创建切面类,用@Aspect标识是切面类,@Component创建切面对象

2.使用@Before  @Around @AfterReturning @AfterThrowing @After注解中写入切入点。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值