spring学习笔记
一、ioc
1、springIOC: 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。 其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。
2、为什么要使用spring IOC: 在日常程序开发过程当中,我们推荐面向抽象编程,面向抽象编程会产生类的依赖,当然如果你够强大可以自己写一个管理的容器, 但是既然spring以及实现了,并且spring如此优秀,我们仅仅需要学习spring框架便可。 当我们有了一个管理对象的容器之后,类的产生过程也交给了容器,至于我们自己的app则可以不需要去关系这些对象的产生了。
3、spring实现IOC的思路和方法 spring实现IOC的思路是提供一些配置信息用来描述类之间的依赖关系,然后由容器去解析这些配置信息,继而维护好对象之间的依赖关系, 前提是对象之间的依赖关系必须在类中定义好,比如A.class中有一个B.class的属性,那么我们可以理解为A依赖了B。既然我们在类中已经定义 了他们之间的依赖关系那么为什么还需要在配置文件中去描述和定义呢? spring实现IOC的思路大致可以拆分成3点: 1)应用程序中提供类,提供依赖关系(属性或者构造方法) 2)把需要交给容器管理的对象通过配置信息告诉容器(xml、annotation,javaconfig) 3)把各个类之间的依赖关系通过配置信息告诉容器
4、spring编程的风格
schemal-based-------xml
annotation-based-----annotation
java-based----java Configuration
5、注入的两种方法 构造方法注入 setter注入 (xml) @Autowire 注入(根据类型和属性名称找) AutowiredAnnotationBeanPostProcessor由这个后置处理器(先根据类型查找,找到多个再根据名称查找) @Resource 根据属性名称注入(与set方法无关) @Inject (set方法上 @javax.inject.Inject)
6、自动装配 上面说过,IOC的注入有两个地方需要提供依赖关系,一是类的定义中,二是在spring的配置中需要去描述。自动装配则把第二个取消了,即我们仅仅需要在类中提供依赖, 继而把对象交给容器管理即可完成注入。 在实际开发中,描述类之间的依赖关系通常是大篇幅的,如果使用自动装配则省去了很多配置,并且如果对象的依赖发生更新我们可以不需要去更新配置,但是也带来了一定的缺点 自动装配的优点参考文档: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-autowire 缺点参考文档: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-autowired-exceptions
7、单例bean依赖原型bean解决方法:(查看UserServiceImpl 原型bean获取) 1)实现ApplicationContextAware接口 每次都从容器中获取 2)使用@Lookup注解+抽象方法
8、bean的生命周期回调 (https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-lifecycle) 1) 实现InitializingBean 接口,容器调用afterPropertiesSet 方法; 2) 实现DisposableBean接口,容器调用destroy方法; 3) init方法 在xml文件中配置(或在@Bean注解中配置) 4) 方法上添加PostConstruct注解
9、懒加载 使用@Lazy注解,第一次使用时才创建bean,(默认不是lazy,应用启动时就创建)
10、@Primary 注解: 一个接口有多个实现时,指定其中一个为primary(主要),依赖注入通过类型注入时,会注入添加了@Primary注解的那个。
11、@Profile注解: 环境(如开发环境,生产环境) 在applicationContext中设置哪个环境生效 applicationContext.getEnvironment().setActiveProfiles(“dev”)
12、FactoryBean: 一个接口,如果一个类实现了该接口,并且放到了容器中,从容器中获取时获取到的是getObject()方法返回的对象而不是该对象本身,如果要获取本身名称前面加个“&“ 应用场景: FactoryBean 通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑, 用xml配置比较困难,这时可以考虑用FactoryBean(对真正的bean做了一层包装); 很多开源项目在集成Spring 时都使用到FactoryBean,比如MyBatis3
二、AOP
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop
1、Aop是什么 与OOP对比,面向切面,传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性问题,这些横切性的问题和我们的主业务逻辑关系不大, 这些横切性问题不会影响到主逻辑实现的,但是会散落到代码的各个部分,难以维护。AOP是处理一些横切性问题,AOP的编程思想就是把这些问题和主业务逻辑分开, 达到与主业务逻辑解耦的目的。使代码的重用性和开发效率更高。
aop的应用场景:
日志记录
权限验证
效率检查
事务管理
exception
3、springAop的底层技术 织入时期| JDK动态代理|CGLIB代理 —|—|— |编译时期的织入还是运行时期的织入? | 运行时期织入 |运行时期织入 初始化时期织入还是获取对象时期织入?| 初始化时期织入 |初始化时期织入
4、springAop和AspectJ的关系
Aop是一种概念
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件
springAop、AspectJ都是Aop的实现,SpringAop有自己的语法,但是语法复杂,所以SpringAop借助了AspectJ的注解,但是底层实现还是自己的。
spring AOP提供两种编程风格
@AspectJ support ------------>利用aspectj的注解
Schema-based AOP support ----------->xml aop:config 命名空间
证明:spring,通过源 码分析了,我们可以知道spring底层使用的是JDK或者CGLIB来完成的代理,并且在官网上spring给出了aspectj的文档,和springAOP是不同的
5、spring Aop的概念
aspect:一定要给spring去管理 抽象 aspectj->类(用aspect风格就是切面类)
pointcut:切点表示连接点的集合
(PointCut是JoinPoint的谓语,这是一个动作,主要是告诉通知连接点在哪里,切点表达式决定 JoinPoint 的数量)
Joinpoint:连接点 目标对象中的方法
JoinPoint是要关注和增强的方法,也就是我们要作用的点)
Weaving :把代理逻辑加入到目标对象上的过程叫做织入
target 目标对象 原始对象
aop Proxy 代理对象 包含了原始对象的代码和增加后的代码的那个对象
advice:通知 (位置 + logic)
advice通知类型:
Before 连接点执行之前,但是无法阻止连接点的正常执行,除非该段执行抛出异常
After 连接点正常执行之后,执行过程中正常执行返回退出,非异常退出
After throwing 执行抛出异常的时候
After (finally) 无论连接点是正常退出还是异常退出,都会执行
Around advice: 围绕连接点执行,例如方法调用。这是最有用的切面方式。around通知可以在方法调用之前和之后执行自定义行为。
它还负责选择是继续加入点还是通过返回自己的返回值或抛出异常来快速建议的方法执行。
6、使用
1)使用XML配置启用@AspectJ支持
要使用基于xml的配置启用@AspectJ支持,可以使用aop:aspectj-autoproxy元素:<aop:aspectj-autoproxy/>
java配置使用@EnableAspectJAutoProxy注解 (proxyTargetClass: 默认false使用jdk动态代理,true:使用cglib动态代理)
2)声明一个Aspect:申明一个@Aspect注解类,并且定义成一个bean交给Spring管理。
3)申明一个pointCut:切入点表达式由@Pointcut注释表示。切入点声明由两部分组成:一个签名包含名称和任何参数,以及一个切入点表达式,
该表达式确定我们对哪个方法执行感兴趣
4)申明一个Advice通知:
advice通知与pointcut切入点表达式相关联,并在切入点匹配的方法执行@Before之前、@After之后或前后运行。
7、切入点表达式:
1)execution:用于匹配方法执行连接点 格式:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:方法的可见性,如public,protected;
ret-type-pattern:方法的返回值类型,如int,void等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;
示例:
@Pointcut("execution(* com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和类的任意方法
@Pointcut("execution(public * com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和类的public方法
@Pointcut("execution(public * com.chenss.dao.*.*())")//匹配com.chenss.dao包下的任意接口和类的public 无方法参数的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")//匹配com.chenss.dao包下的任意接口和类的第一个参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(public * *(..))")//匹配任意的public方法
@Pointcut("execution(* te*(..))")//匹配任意的以te开头的方法
@Pointcut("execution(* com.chenss.dao.IndexDao.*(..))")//匹配com.chenss.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.chenss.dao..*.*(..))")//匹配com.chenss.dao包及其子包中任意的方法
2)within
within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等
@Pointcut("within(com.chenss.dao.*)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法
3)args
args表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关
@Pointcut("args(java.io.Serializable)")//匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配
@Pointcut("@args(com.chenss.anno.Chenss)")//接受一个参数,并且传递的参数的运行时类型具有@Classified
4)this
JDK代理时,指向接口和代理类proxy,cglib代理时 指向接口和子类
(jdk代理:代理对象和目标对象都实现同一个接口,CGLIB:代理对象是目标对象的子类)
5)target 目标对象
8、JoinPoint: 连接点(可以获取目标对象target 和 代理对象this)
9、切面类Aspect:
默认是单例,也是指定为原型模式。
原型模式:
每次aop都会生成新的切面对象,
可指定一个目标对象,只为该目标对象的指定方法使用多例aop。
示例: @Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
三、代理
代理:
静态代理: 聚合 继承 (不确定的情况下容易产生类爆炸)
动态代理:
模拟动态代理:运行时通过程序生成.java文件,再编译成.class文件,再通过类加载器创建对象 见:ProxyUtil
jdk动态代理: 根据接口反射拿到接口的包信息,方法信息,参数信息,返回类型信息等 生成byte[]字节码,再调用Native方法生成Class对象(类的类对象),(内部持有InvocationHandler)
生成的代理类会继承Proxy类,并实现给定的接口,再通过Class对象获取构造函数(构造函数参数为:InvocationHandler类型)创建代理对象。
cglib动态代理:通过继承,生成一个子类对象
四、spring的拓展点
1、BeanPostProcessor:
bean后置处理器 bean实例化之后调用 可以替换bean,将原生bean换成代理bean 如:CommonAnnotationBeanPostProcessor可以完成生命周期的回调(处理@PostConstruct,@PreDestroy等注解)
AutowiredAnnotationBeanPostProcessor完成依赖的注入,
每个bean在实例化的时候都会遍历所有的BeanPostProcessor执行相应的回调方法完成特定功能(代理对象的生成,依赖注入,生命周期回调)
2、BeanFactoryPostProcessor:
BeanFactory的后置处理器,如:ConfigurationClassPostProcessor 完成对配置类的解析,
处理@Configuration,@Bean, @ComponentScan @Import等注解 注册beanDefinitionMap中
其中 BeanDefinitionRegistryPostProcessor 是BeanFactoryPostProcessor的一个子接口,ConfigurationClassPostProcessor也是该接口的实现
在执行BeanFactoryPostProcessor的回调时,
先执行BeanDefinitionRegistryPostProcessor的回调(有排序的先,无排序的后)
再执行BeanFactoryPostProcessor的回调,(有排序的先,无排序的后)
3、ImportBeanDefinitionRegistrar接口:
用在@Import注解上,通过该接口可以由程序员手动注册一个BeanDefinition到容器中(Mybatis的@MapperScan注解就用到了这个接口)
4、ImportSelector接口:
用在@Import接口上,返回多个类名(全名)当作配置类,注册到beanDefinitionMap
5、根据类型自动装配
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE):根据属性(set方法)完成装配(不需要@Autowire注解和@Resource注解)
6、AOP:
@EnableAspectJAutoProxy注解导入了一个类AspectJAutoProxyRegistrar(是ImportBeanDefinitionRegistrar的子类),ConfigurationClassPostProcessor解析配置类时
会调用AspectJAutoProxyRegistrar的registerBeanDefinitions方法,该方法中注册了一个BeanPostProcessor后置处理器:AnnotationAwareAspectJAutoProxyCreator,创建bean时会执行
AnnotationAwareAspectJAutoProxyCreator的 postProcessAfterInitialization 方法,该方法会判断是否需要生成aop代理对象。
7、ImportAware接口:
和@Import注解配合导入的类实现ImportAware接口,可以拿到所有的注解元素
五、mybatis整合spring
1、@MapperScan注解:
ConfigurationClassPostProcessor 在解析配置类时会拿到所有的注解,并递归遍历(找到MapperScan的@Import注解)
@Import注解导入了MapperScannerRegistrar
而 MapperScannerRegistrar 实现 ImportBeanDefinitionRegistrar这个接口(spring提供的一个扩展点,可以由用户手动注册一些BeanDefinition)
MapperScannerRegistrar 注册了一个MapperScannerConfigurer(BeanDefinitionRegistryPostProcessor子类,也是spring的一个扩展点)的BeanDefinition
MapperScannerConfigurer通过ClassPathMapperScanner可以完成Mapper的扫描,再创建MapperFactoryBean, 复写getObject方法返回一个Mapper接口的代理对象
2、日志: 使用slf4j,具体的实现可以用logback(log4j,log4j2)
3、类关系:创建的mapper代理对象中包含InvocationHandler类型的h, h中有一个sqlSession(SqlSessionTemplate类型),sqlSession中有一个sqlSessionProxy(这个也是代理对象,SqlSession类型),
它的InvocationHandler是SqlSessionInterceptor(SqlSessionTemplate的内部类),执行sql时最终会执行到SqlSessionInterceptor的 invoke方法,
创建SqlSession(DefaultSqlSession)执行sql,然后关闭SqlSession。(如果用到了spring的事务,sqlSession的关闭会交给spring处理)
4、MapperFactoryBean 是一个FactoryBean,同时继承了SqlSessionDaoSupport(是InitializingBean的子类)容器会回调afterPropertiesSet方法,该方法会调用checkDaoConfig方法
继续调用 configuration.addMapper(this.mapperInterface)方法 继续调用到parser.parse()方法 继续调用到parseStatement(method); 继续调用assistant.addMappedStatement方法,
继续调用 configuration.addMappedStatement(statement); 调用mappedStatements.put(ms.getId(), ms);方法 将mapper中的方法和注解中的sql语句信息存到mappedStatements这个Map中。
执行mapper的方法调用时,会从mappedStatements取出对应的sql信息。
5、SqlSessionTemplate的创建:ClassPathMapperScanner在扫描mapper时,创建MapperFactoryBean时,设置了 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);(根据类型装配,就是根据set方法装配)
容器会调用setSqlSessionFactory (是MapperFactoryBean的父类SqlSessionDaoSupport的方法,因为是set方法,容器会调用) 方法创建SqlSessionTemplate
6、SqlSessionTemplate 内部有一个SqlSession 类型的代理对象(实际是DefaultSqlSession类型的动态代理对象),代理对象的InvocationHandler是SqlSessionTemplate的内部类
SqlSessionInterceptor,执行sql都是通过该类invoke方法.
7、事务处理: 通过aop实现,执行目标方法前先获取一个连接并开启事务,然后把连接放入到TransactionSynchronizationManager中(ThreadLocal),mybatis执行sql获取连接时通过TransactionSynchronizationManager
获取刚才放入的连接,然后执行sql,连接的关闭不用管而是由spring处理。
六、事件监听
监听器实现ApplicationListener 接口
七、Spring Mvc
1、创建Controller:
1)使用@Controller注解
2)实现Controller接口
2、DispatcherServlet:
维护了一个List<HandlerMapping> handlerMappings;
HandlerMapping:维护mapping和该mapping的处理器,是一个接口,有多种实现:如处理@Controller@RequestMapping的RequestMappingHandlerMapping
接收到一个请求时会遍历handlerMappings找到处理该请求的HandlerExecutionChain
A.初始化流程:
1)执行静态代码块,会加载一个资源文件:DispatcherServlet.properties,该配置文件中定义了一些HandlerMapping,HandlerAdapter,HandlerExceptionResolver,ViewResolver
2)tomcat调用init方法(在父类HttpServletBean中)
3)init调用initServletBean方法(在父类FrameworkServlet中)
4)initServletBean调用initWebApplicationContext方法
5)initWebApplicationContext调用onRefresh方法(DispatcherServlet中)
6)onRefresh调用initStrategies方法
7)initStrategies初始化HandlerMappings和各种解析器
initHandlerMappings方法解析:
先从bean工厂中获取handlerMapping(一个集合),如果没获取到就使用默认的(从加载的DispatcherServlet.properties配置文件中获取)
放到了handlerMappings集合中
B.请求处理流程:
Servlet的service方法根据请求方式调用:doGet(doPost...)方法(在FrameworkServlet中),再调用processRequest方法再调用doService方法(在DispatcherServlet中)
再调用doDispatch方法,再调用getHandler方法获取处理该请求的HandlerExecutionChain,
再创建HandlerAdapter,再调用HandlerAdapter的handle