Spring原理与事务

Spring 基础

Ioc

有什么好处

  1. 使用者不需要关注对象的构建过程,我们只要借助ioc提供的方法,然后使用就可以了

  2. 如果我们的系统有特别复杂的依赖关系,spring也能够帮助我们自动管理

  3. 基于接口注入,实现类想换哪个就换哪个,系统的耦合度低

设计

主要基于BeanFactoryApplicationContext两个接口

其中BeanFactory是Ioc最底层的接口,但是ApplicationContext对它做了很多有用的扩展,所以大部分我们会时候后者

BeanFactory

public interface BeanFactory {

	String FACTORY_BEAN_PREFIX = "&";

    //下面是几个获取bean的方法,可以通过名称和类反射来获取
	Object getBean(String name) throws BeansException;

	<T> T getBean(String name, Class<T> requiredType) throws BeansException;

	Object getBean(String name, Object... args) throws BeansException;

	<T> T getBean(Class<T> requiredType) throws BeansException;

	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

	<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

	<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

	boolean containsBean(String name);

    //单例模式
	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    //与单例模式相反
	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    //按type类型匹配
	@Nullable
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;

	@Nullable
	Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;

    //获取别名
	String[] getAliases(String name);

}

​ 其实spring官方的源码注释还是相当详细的,如果你看到的是一些奇奇怪怪的变量名,比如var1,var2,你试着下载源码,这个时候看到的才是spring的源码。比Mybatis注释详细的多,我一度怀疑自己没有下载mybatis的源码。太详细了,再夸一句spring牛逼

ApplicationContext

​ 翻译一下:应用的上下文

ClassPathXmlApplicationContext其中的一个实现类,负责读取类路径下的xml配置的bean信息

Spring Ioc 初始化过程

​ 先定义再初始化

定义的步骤:(整个过程是没有进行任何初始化的)

  1. Resource 资源定位,通过开发者的配置或注释,定位到资源也就是我们定义Bean的地方
  2. BeanDefinition,把定位的Bean信息保存到BeanDefinition
  3. BeanDefinition的注册,把里面的信息发布到Ioc容器中

​ 注意,完成这些步骤,Bean只是在容器中被定义了,但是没有完成初始化,更没有什么依赖注入,也就是没有把配置的信息放到Bean里面

​ Spring Bean 有一个配置选项lazy-init,是否延迟初始化,如果延迟初始化,那么只有我们在Ioc容器里面获取它的时候,才会进行Bean的初始化,完成依赖注入

关于切面的其他收获

​ Ioc容器的本质就是为了管理Bean,如果我们的bean在初始化或者销毁的时候,需要一些自定义的过程,那么我们就需要用到Bean的生命周期

​ Idea帮助我们自动填充的代码,比如我们在@Override一个方法的时候,idea会帮助我们调用上层的方法,代码还是比较臃肿的,我们可以看看上层是怎么实现的,只要和上层做一样的实现,处理自己事情就可以了

​ 还是有点难的,不过我也收获了不少

​ 我为了偷懒,想着用切面查看一下这些方法执行前后的参数,所以定义了一个切面类

@Aspect//切面注解
@Component//让spring管理
@Slf4j//日志
public class MethodAspect {

    @Around(value = "@annotation(methodLog)")
    //首先要注意的就是这个指定,这个是下面方法参数的参数名,所以是小写的
    public Object doAround(ProceedingJoinPoint joinPoint, MethodLog methodLog) throws Throwable {
        String name = methodLog.value();//通过注释我们获得的属性
        long start = System.currentTimeMillis();
        //以下的这些方法,是无法获取你给方法参数起的名字是什么
        Object aThis = joinPoint.getThis();//得到执行这个方法的对象本身
        Object[] args = joinPoint.getArgs();//得到方法传入的参数值
        Signature signature = joinPoint.getSignature();//得到方法的签名,类名 方法名 方法参数类型
        //我把上面这个打印出来,才真正的理解了签名是什么意思,就是我们可以通过签名唯一的定位一个方法
        Object target = joinPoint.getTarget();//返回的结果
        log.info("{} : {}",name,target);
        // log.info("{} {} {} {}",args,aThis,joinPoint.getTarget(),name);
        try {
            return joinPoint.proceed();//让真正的方法执行
        } finally {
            long t = System.currentTimeMillis() - start;//这个可以统计一个方法执行说需要的时间
        }
    }
    //后来我发现,真不如去看源码,如果你看了函数还猜不出方法是什么意思,可以直接看源码的注释,这是spring最方便的一点,没有它,我们想要学会这个框架,难度还是非常离谱的

}

​ 不过这种方法其实是不行的,因为spring boot对bean的拦截太多了,到处都充满了它自己的类,所以我们还是得按照书上的方法,老老实实来,下面我就直接给出一个尽可能详细的注释,来描述bean的整个生命周期

@Component
public class InnerBean implements BeanPostProcessor {

    @Override//对象开始示例化
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override//对象实例化完成
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}

Bean的生命周期

  1. 初始化

  2. 依赖注入

  3. setBeanName BeanNameAware接口

  4. setBeanFactory BeanFactoryAware接口

    • 4和5一般是spring自己用的方法,用来获取bean和bean名称
  5. setApplicationContext ApplicationContextAware接口

    • 需要容器实现ApplicationContextAware接口,它才会起作用
    • 主要是这里有ApplicationContext作为接口参数
  6. postProcessBeforeInitialization

    • 针对所有bean而言
  7. afterPropertiesSet InitializingBean接口

  8. @PostConstruct

    • 自定义初始化方法
  9. postProcessAfterInitialization

    • 针对所有bean而言
  10. 生存期。。。。。。。

  11. destroy DisposableBean接口

  12. @PreDestory

    • 自定义消耗方法

Bean 装配

构造器注入

​ 利用构造器,反射注入,不推荐,因为参数多的话,可读性不好

setter注入

​ 利用无参数构造器,结合set方法构造,推荐

接口注入

​ 用于注入系统外界的配置

spring 提供的3种配置方法

  • xml配置(基本废弃)
  • java接口或类(代替xml完成复杂的配置)
  • 隐私Bean发现机制和自动装配原则(推荐,优先使用)

xml配置

​ 如果我们需要配置的存在不定形式的嵌套,也可能不嵌套,spring用ref标签实现了这样的设计要求
​ spring在xml配置里,使用过命名空间,有点类似与vue在html标签里扩充的属性,简化了我们编写,但其实可读性才是最重要的,因为是xml唯一的优势,还是一个优势是标签的互相引用,可以介绍大量重叠配置,而且可以自由嵌套

注解装配

组件扫描

定义资源,让spring扫描特点的包

自动装配

通过注解来定义,使得一些依赖关系可以通过注解来完成

@Component

​ 这个注解有属性value,代表它在spring中的id,如果没有指定就是类名的首字母小写

@ComponentScan

​ 在spring boot的自动配置下,这个注释不是很常用,但可以帮助我们扫描,spring boot默认没有扫码到的包,它默认会扫码这个包下的其他的类,同时也可以指定包扫描的位置

​ 如果多次指明要扫码的包,会导致重复创建代理对象,这个是很麻烦的,不要出现这种情况,除非你真的需要,所以在spring boot中,我们选择了@Component

​ 两种扫码机制,如果你类的位置固定不动,那么用包扫描,可读性好,否则用类扫描,适合于那种类位置,包变动的

AnnotationConfigApplicationContext

​ spring 用这类来实现从注释中获取bean

@Autowired

​ 当spring完成某个Bean的定义和生成,它会寻找被@Autowired注释的资源,然后从自己的Bean中找到对应的,把它注入进去

​ spring推荐我们用接口注入一个bean,这个bean只会出现接口可以调用的方法,如果想让自己在类中定义的方法,在接口中出现,我们可以重写这个方法@Override然后idea会帮我们把这个方法定义在接口中

​ 这个注释可以加在set方法中,如果加载set方法,可以做一些自定义的代码,一般情况是不需要的

​ 构造方法可以使用@Autowired,不过这个注解要写在参数上面

​ 如果一个接口有多个实现类,@Primary标识一个主要的,如果没有具体指定的话,会使用这个类来注入,比如默认数据源。

@Qualifier,在BeanFactory接口中,有按名称注入的接口,但是spring默认采用按类型注入,这个名称就是@Component中定义的名称,没有指定的话,就得是类名首字母的小写

@Bean

​ 第三方包的类怎么处理,有的第三方包把扩充的余地交给了我们,我们可以定义一个类,继承第三方包指定的类或实现第三方包指定的接口,然后用@Component注解,但大部分情况,我们更希望这些配置在yaml中,除非他需要一些逻辑,写在代码中更加简单一些

​ 但,spring还给我们提供了一种注解,让我们在一个类的方法中引入第三方包,它在方法上注解,然后将返回值作为spring的bean,而可以使用Bean的name属性指定名称,而且可以通过注解指定创建和销毁要执行的方法

​ xml中也可以定义bean,然后通过@ImportResourse引入

@Import

​ 我在spring boot中经常看到这个注解,这个用来引入多个配置类

Profile

​ 环境隔离,比如在开发环境,测试环境,线上环境使用不同的数据库,但是配置之后,这些bean需要我们通过指定环境参数来激活,负责是不会加入到Ioc容器的

  • SpringMVC,可以配置web上下文,或者DispatchServlet参数
  • 作为JNDI条目
  • 配置环境变量
  • 配置JVM启动参数
  • @ActiveProfiles

@ActiveProfiles

​ 指定环境的名称,然后注入相应的bean

环境变量

​ spring.profiles.active 配置后spring.profiles.default失效,应该默认环境是开发环境,在后端上线的时候,通过脚本指定好环境,直接启动,覆盖掉其他人员的默认配置

JVM

​ JAVA_OPTS="-Dspring.profiles.active=test"

条件化装配和属性配置

​ 我影响中spring boot对这个地方做了升级,属性用yaml,条件也提供的新的注解,我们会写在spring boot的专属笔记中

作用域

@Scope注解,可以指定bean的作用域

singleton 单例

​ 默认作用域

prototype 原型

​ 获取一次装配一个新的实例

session 会话

​ 一次会话过程创建一次

request 请求

​ 一次完整的请求对应一个

Sring EL

​ spring非常强大的表达式解析器,可以完成属性的复杂装配,比如配置文件的值和我们想要的值有一定的区别,但是为了配置文件的可读性,为了配置文件的简易性,我们可以用 Sring EL

面向切面编程

术语概念

Aspect 切面

​ 指定切面的工作环境,比如controller通配符,自定义注解

Advice 通知

before前置通知,在执行原有方法之前执行

after后置通知,执行完方法后,无论方法是否出现异常都会执行

afterReturning返回通知,无异常时执行,比如事务提交

afterThrowing异常通知,出现异常时执行,比如事务回滚

around环绕通知,可以看成前面几个通知的组合写法(推荐)

Introduction 引入

​ 容许我们扩充现有的类,让它有更多的方法

Pointcut 切点

​ 告诉spring在什么时候切入

join point 连接点

​ 具体要拦截的,比如一个方法,还有方法的签名,参数等

Weaving 织入

​ 生成代理对象,并且将切面内容放入到流程中

​ 静态代理:在编译class文件的时候生成逻辑代码

​ 动态代理:ClassLoader类加载的时候生成的逻辑代码,但是在代码运行前生成,CGLIB,spring动态代理,运行期间生成


​ 注意:spring是方法级别的AOP

AOP的使用

@Aspect

​ 注解在类上,表示这个类定义了切面的细节

指示器

​ 指示器可以指定哪些方法被拦截

​ execution 正则表达式匹配

​ @annotation 注解

​ args(xxx,yyy)可以把方法中名为xxx和yyy的参数直接传递给这个切面方法

ProceedingJoinPoint

​ 我们想了解一个类中的属性代表的值是什么含义,最好的方法是,看文档,看博客,或者用debug去实际的方法中对照一些,这个值最好你可以用到的,可以获取的值,否则是没有什么意义的,一般框架肯定会把有用值的get方法提供出来,所以,不要自通过一个一个打印get方法的值,去猜测了

织入

​ 如果我们类实现了一个接口,那么spring会使用jdk的动态代理,否则spring会使用CGLib的代理,所以我们是否定义了接口,和AOP是没有任何关系的

@DeclareParents

​ 让一个类被动的实现一个接口,然后我们可以在切面中调用这个接口提供的方法,通过强制类型转换就可以

xml配置

​ 由于这个没什么人用,所以不做笔记,但是记录一下,因为xml中使用一下符号,比如&&,||,!,<,>会有一定的问题,所以spring在这里做了转义,比如用and,or,not来替代,这个地方可以我们可以学习一下,而且单词的可读性更好

多个切面

@Order

​ 在spring中,一切涉及到顺序的问题,几乎都可以用这个注解来解决

执行顺序

​ 就是责任链的执行顺序,一层套一层


​ 不得不说,书讲的实在太少了,可以作为入门算是不错的书,至少我能看懂

数据库编程

JDBCTemplate

​ spring会给常用的技术提供模板化的支持,这也是一种设计模式

​ 技术不常用,不做赘述

MyBatis-Spring

​ 将mybatis和spring结合起来

public class SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    
  private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
  private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();

  private Resource configLocation;//MyBatis的配置文件

  private Configuration configuration;//mybatis的全局配置对象

  private Resource[] mapperLocations;//mapper配置路径

  private DataSource dataSource;//数据库

  private TransactionFactory transactionFactory;//事物管理器

  private Properties configurationProperties;//配置属性

  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();//四大对象

  private SqlSessionFactory sqlSessionFactory;//sqlSession的工厂,四大对象

  private String environment = SqlSessionFactoryBean.class.getSimpleName();

  private boolean failFast;//是否快速失败

  private Interceptor[] plugins;//插件

  private TypeHandler<?>[] typeHandlers;//类型转换器

  private String typeHandlersPackage;//类型装换器在的包

  @SuppressWarnings("rawtypes")
  private Class<? extends TypeHandler> defaultEnumTypeHandler;//默认枚举处理器

  private Class<?>[] typeAliases;//别名处理器

  private String typeAliasesPackage;//别名处理器所在的包

  private Class<?> typeAliasesSuperType;

  private LanguageDriver[] scriptingLanguageDrivers;//数据库厂商标识

  private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;

  private DatabaseIdProvider databaseIdProvider;

  private Class<? extends VFS> vfs;//unix文件操作

  private Cache cache;//缓存

  private ObjectFactory objectFactory;//对象工厂

  private ObjectWrapperFactory objectWrapperFactory;

}

MapperFactoryBean

​ 怎么把mybatis对接口的动态代理和spring的动态代理结合起来,就是这个类起的作用


​ 书中讲的特别浅,在spring boot的时代下,已经过时了,不过作为一步入门的书,我觉得还是非常好的,至少我非常有兴趣能看下去,而且学到了不少东西

事务管理

​ 这块我一直没有遇到复杂的应用场景,所以打算好好学习一下

PlatformTransactionManager

public interface PlatformTransactionManager extends TransactionManager {
    //创建事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    //提交事务
    void commit(TransactionStatus status) throws TransactionException;

    //回滚事务
    void rollback(TransactionStatus status) throws TransactionException;
}

DataSourceTransactionManager

@Transactional 声明式事务

配置项备注
isolation隔离级别,非常重要,后面会详细说
value/transactionManager一个需要实现PlatformTransactionManager接口的Bean
propagation传播行为,设计到事务方法之间的相互调用
timeout超时时间
readOnly只读事务
(no)rollback(不)会回滚的事务类型
(no)rollbackForClassName(不)同上,但用的是字符串

​ 这个注解让我收获到一点,注解是可以直接配置枚举类的

原子性 (原子不可分割)

​ 整个事务的所有操作,要么全部成功,要是有一个失败,其他所有的都会变回原来未执行事务的状态

一致性

​ 读写一致

隔离性

​ 事务的隔离程度,多个事务

持久性

​ 事务完成后,该事务对数据库所做的更改会被保存

丢失更新

  1. 两个事务同时开启,其中一个事务发起回滚(mysql基本不会出现这种情况)
  2. 两个事务同时操作同一个数据和java并发修改同一个数据的影响一样

隔离级别

​ 事务隔离级别,多个事务同时操作一个数据的时候涉及

​ 看别人的博客,写的比书上的好MySQL相关问题

如何选择隔离级别

​ 依据:看性能和数据的一致性哪个对你的这个业务更重要

​ 要求数据库高度精确不能出错而且性能要求还特别高,比如秒杀系统,是不会通过隔离级别来控制数据库的一致性的

​ 此外,在大部分环境中,还选择读/写提交的隔离级别,这种有助于提高并发,而且压制了脏读

​ 如果我们业务的并发特别小,或者是那种对性能要求不是很高,但是要保证数据一致性的,我们可以才用序列化的方法,保证数据库不出错

​ 如果我们不指定@Transactional的隔离级别,他会采用默认的隔离级别,数据库默认的隔离级别,也就是说,对于MySQL是可重复读但是对Oracle这种只支持读/写提交序列化两种的,支持的是读/写提交

传播行为

​ 方法之间的调用事务策略的问题

传播行为含义
REQUIRED默认传播行为,如果当前不存在事务,就开新事务,否则,就用之前的事务
REQUIRES_NEW无论什么情况都会新开启一个事务
NESTED和REQUIRES_NEW类似,但是可以回滚到指定的保存点

事务失效

​ 因为@Transactional的底层实现是AOP,所以static和非public方法是无法使用的,static一般我们是不会用的,但是我们有注意,如果我们调用的是私有方法,那么就得小心了,调用私有方法肯定是同层之间调用,注意不要在私有方法上用@Transactional。另外,这也告诉我们,AOP是不支持这些行为的,而且最关键的是自调用的问题,就是一个类的函数互相调用,基于AOP的实现原理,同一个类的方法直接调用,是不会出现代理类的,所以会出现事务失效,所以,AOP只支持,不同类公共非静态方法

​ 那怎么解决,其实很好解决,我们有为业务专属的service,一般而言,业务专属service操作好几个数据库,但是单个service只操作和自己精密相关的数据库,和DDD比较类似,但是我这种可以直接操作数据库的修改,而不是通过对象来修改,所以业务层和普通service是一个层级的,我们可以把mapper注入到业务service,BDD的核心就在于,能让对象来操作的,就让对象来操作

​ 我们可以通过查看mybatis的日志,Creating a new SqlSession来看事务的开启情况是什么样的

​ 此外,如果我们想让两个在一个事务中,就不能在controller层写方法了,而且我现在觉得是真的不好,而且没什么用

​ 事务的粒度也是需要我们把握的,因为事务和锁一样,是对影响速度影响比较大的,所以我们应该想办法缩小事务和锁的粒度,你要记住,io和数据库是非常可怕的,你要尽可能的减少对他们的使用,要多用内存,redis,怎么解决,分开方法就行,小问题。而且你要注意的是,分隔的类,只要是不同类之间的互相调用,就可以避免事务出现问题,并不是说,需要你加多少多少层

​ 异常处理,你捕获的异常,是你要真正能处理了的,如果你不行,那么你必须处理后继续抛出,在spring中,如果你自己处理了异常,但是不抛出,就会出现spring不会帮助你回滚事务的情况

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值