Spring学习经验
Spring
各位读者朋友你好,我是你的好朋友IT黑铁,在最近一段时间内,我完成了Spring的初步学习,总结了一些学习经验,如有错误还望指正!
学习途径:黑马程序员的bilibili视频。
(一)认识Spring
Spring Framework:底层框架,其他spring技术均依赖于它,通常说spring就指它,2004年Spring(1.0,配置开发)创新于EJB思想基础,2.0系列引入注解,3.0可以不写不配,4.0跟着jdk升级api,5.0全面支持jdk8。
Spring MVC:Spring的web应用技术。
Spring Boot:Spring的快速开发启动技术。
SpringCloud:Spring的微服务开发技术。
(二)解决问题:
简化开发
框架整合
(三)核心思想
4.0系统架构图
IoC(Inversion of Control)控制反转思想
解决问题:解决对象内部耦合,解决对象锁死,对象获取途径转到从外部核心容器获得,即将对象让spring核心容器(Core Container)管理,此时对象被称作Bean。
DI(Dependency Injection)依赖注入思想
解决问题:解决Bean与内部对象的对应关系,并将Bean正确注入。
(四)关于Bean
配置管理Bean:
实现思路:提供注入方法(setter或构造方法),配置绑定关系。
普通bean配置:
注意:name配置别名,scope配置作用范围,bean默认单例,所以实体对象一般不交给spring管理,创建bean需要一个可访问构造方法。
静态工厂配置:
注意:静态工厂里生产bean的方法需要是静态的。
实例工厂配置:
注意:实例工厂里生产bean的方法不是静态的。
实现FactoryBean接口配置:
注意:FactoryBean的产品初始化后受spring管理,之前的阶段都不会,并且它创建的单例对象不会存储在BeanFactory的singletonObjects成员中,而是另一个factoryBeanObjectCache成员中。并且按名字获取bean时获得的是产品对象,要想获得工厂对象可以使用&名字获取也可以按类型获取。
Bean生命周期:
init、destroy
第一种:在bean对象里手造init和destory方法。
注意:要想观察destory方法,得先关闭容器,一种办法是调用ConfigurableApplicationContext接口的实现类ClassPathXmlApplicationContext的close方法关闭虚拟机(暴力关闭容器),一种是为容器加一个关闭钩子,容器调用registerShutDownHook方法注册钩子。
第二种:在bean对象里实现InitializingBean,DisposableBean两个接口后,就无需像第一种方法配置init-method或destory-method指明具体方法。、
深入Bean生命周期:
postprocess sBeforeInstantiation(实例化前,替换原本bean,null不替换)
construct(构造)
@postProcessAfterInstantiation(实例化之后,若返回false跳过依赖注入)
postProcessProperties(依赖注入,如@Autowired、@Value、@Resource)
postProcessBeforeInitialization(初始化之前,替换原本bean,null不替换,如@postConstruct、@ConfigurationProperties)
init(初始化){注意spring三种执行方法,一种是@PostConstruct,一种是实现InitializingBean接口,一种是@Bean注解指明该bean的初始化方法(initMethod),优先级从高到低}
postProcessAfterInitialization(初始化之后,替换原本bean,如代理增强 )
postProcessBeforeDestruction(销魂前执行)
destroy(销魂){注意spring三种执行方法,一种是@PreDestory,一种是实现DisposableBean接口,一种也是通过@Bean指明该bean的销毁方法(destoryMethod),优先级从高到低}
四个阶段、六个功能增强,通过实现一下接口:
Scope:singleton,prototype,request(每一个请求),session(同一个浏览器),application(同一个网页)
singleton:spring容器创建时创建所有单例,spring容器关闭时销魂
prototype:每次使用时创建,不由容器管,自行创建
其中session的超时时间可以在spring中设置,而在spingboot中没有对application的销魂处理。
注意:Scope失效情况一,当单例对象内部注入多例对象时且调用getter方法获得多例对象时,多次获得的多例对象是不变的,原因是因为依赖注入只进行了一次,getter方法获得都是第一次依赖注入的多例对象。解决办法一种是为单例对象中的成员多例对象上加@Lazy(注入代理对象),每次调用多例对象的方法都由代理生成新的多例对象;第二种是在多例对象类上加@Scoper的proxyMode=ScopedProxyMode.Target_CLASS,原理与第一种相同;第三种是通过ObjectFactory注入,getter方法里使用工厂的getObject方法;第四种使用sping的容器ApplicationContext,getter方法使用getBean方法。四种办法都是推迟其他scope bean的获取。
依赖注入的两种方式:
setter注入:
在bean配置里加入prooerty标签,name代表属性名,ref注入引用类型,value注入简单类型。
构造器注入:
在bean配置里加入constructor-arg标签,name代表属性名,ref注入引用类型,value注入简单类型,不好的设计导致是形参,所以有使用type标明属性,”int”,”java.lang.String”,或用index设置位置。
注意:setter注入有概率不进行注入导致null对象产生,但setter注入灵活性强,建议使用setter注入!
自动装配:
注意:autowired常用byType,byName,不能对简单类型注入,优先级低于setter注入和构造器注入。
集合注入:
注意:list、array即是<set>标签替换为<list>、<array>,map则是将<set>替换为<map>后,注入信息写为<entry key=”?”,value=”?”>,Properties则是将<set>替换为<props>,注入信息写为<prop key=”?”>?</prop>,其中list和array可以混用但不建议。
配置管理第三方Bean:
核心思路:找到第三方Bean的对象方法,判断使用setter注入还是构造器注入,接着注入信息里根据不同第三方bean的要求格式写。
配置管理properties文件:开启context命名空间
使用context空间加载properties文件,使用${}获取properties文件属性
注意:若properties文件属性名与系统环境属性冲突,会优先系统环境属性,解决方案在<context>标签里加入system-properties-mode=”NEVER”,不加载系统环境变量属性,如果要加载多个文件则可以location里写入多个文件,以逗号分隔,同时也可以使用通配符,例classpath*:*.properties,冒号前的星号表示从工程所有文件包括jar包中搜索并加载,冒号后面的星表示加载所有properties文件。
容器类层次结构图:
注解管理bean:
@Component(以下是三个衍生注解,功能一样,方便理解)
@Service
@Repository
@Controller
@Configuration(代表配置类,顶替配置文件,在配置类上加)
@ComponentScan(注解扫描,顶替<context:component-scan base-pachage=”com.itdaling”,在配置类上加,一般是在主配置类上加,扫描所有注解,有@Configuration的注解被扫描到被视作配置类,称作扫描式,其中可以使用@ComponentScan.Fillter设置过滤)
@Scope(作用范围)
@PostConstruct(生命周期,构造方法后,方法上加)
@PreDestory(销毁前)
@Autowired
@Qualifier(胜任的bean,指定注入bean,配合Autowired使用)
@Primary(与@Qualifier功能相同,但是加载bean上,优先级最高为@Qualifier)
@Value(注入简单类型值)
@PropertySource(指定属性文件,在配置类上加,不支持使用通配符)
@Bean(代表当前方法返回值是一个bean,通常用于管理第三方bean)
@Import(导入配置,导了配置类后就无需在多写@Configuration)
@RunWith(设置类运行器)
@ContextConfiguration(上下文配置,指Spring环境配置)
@Aspect(表示该类是AoP通知类)
@Order(切面执行顺序,加在类上,加在@Bean和方法上无效)
@Pointcut(定义切点)
@Before(功能执行之前,功能执行时机)
@After(功能执行之后)
@Around(环绕通知)
@AfterReturning(方法正常运行完)
@AfterThrowing(方法抛异常后)
@EnableAspectJAutoProxy(告诉spring有用注解开发的AoP)
@Transactional(通常写在业务层接口上,当前接口所有方法开启事务)
@EnableTransactionManagement(开启注解式事务驱动,写在主配置类上)
@Indexed(解决组件扫描所有class资源(包括jar)耗时长,spring5.0的优化,@Component间接标注了该注解,导入spring-context-indexer依赖,会找index注解找到就加入如下文件(提前扫描),如果META-INF/spring.components存在,以它为准加载bean,否则走遍历流程)
@InitBinder(标注在方法上,为绑定器添加自定义扩展的转换器)
@ModelAttribute(将方法返回值补充到ModelAndViewContent模型数据中)
@ControllerAdvice(为所有控制器添加增强功能,有以下三种功能@ExceptionHandler、@InitBinder、@ModelAttribute,并没有使用AOP。对应@InitBinder的增强,标注了该注解的,将会在RequestMappingHandlerAdapter初始化时解析并记录到cache中,而未标注的只在首次执行时解析并记录。对应@ExceptionHandler,为所有控制器方法提供异常处理器方法,只能处理控制器抛出的异常。)
@ConditionalOnMissingBean(@ConditionalOnMissingBean,本项目有注解标注的bean则失效,没有即生效,其他带Conditional的注解多半都是条件判断,见名知意)
@Conditional(条件装配,配合实现Conditional接口的类进行条件判断。另外可以通过组合方式将此注解再灵活生成多情况的注解,更加方便)
AOP思想
Aspect Oriented Programming面向切面编程,一种编程范式,指导开发者如何组织程序结构。
作用:在不惊动原始设计的基础上为其进行功能增强
实现方式:第一种---代理
连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。SpringAoP中为方法的执行。
切入点(Pointcut):匹配切入点的式子,可以使用通配符描述,*表示单个独立的任意符号,..表示多个连续的任意符号,+专用于匹配子类类型。
通知(Advice):在切入点处执行的操作,也就是共性功能。SpringAoP中功能以方法形式呈现。
切面(Aspect):描述通知与切入点的对应关系。(可以在切面里直接定义切入点)
通知类:定义通知的类。
目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作。
代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现。
具体操作:
创建通知类,定义切点,定义共性功能,绑定切面。
注意:spring-aop坐标默认被spring-context依赖。如原始方法有返回值则需要在增强功能后返回值,并且返回类型为Object,同时如果环绕通知未调用原始方法则跳过原始方法的执行。环绕通知使用ProceedingJointPoint(可以通过getSignature,一次执行的签名信息,包含接口和方法,getArgs方法获得原始参数),其他通知使用它的父类JointPoint (getArgs获得原始参数)
切入点书写规范:
注意:代理类没有源码,运行期间直接生成字节码,由类加载器加载。
基于jdk方式代理:
限制:只能针对接口代理,即目标对象实现一个接口,所以可以对目标对象是final的进行增强,目标和代理是兄弟关系。
具体操作:Proxy.newProxyInstance(类加载器,实现接口,方法行为封装),其中方法行为封装实现的invoke方法(代理对象,方法对象,参数),然后在其中使用反射调用实现功能增强(即invoke方法)。
内部实现:代理类通过实现父接口和继承Proxy类(提供定义好的方法行为封装),使用接口成员构造方法,实现父接口的方法时,调用方法行为封装的invoke方法,在用户实现方法行为封装invoke方法后实现功能增强,实现invoke方法过程中调用目标对象方法的也是invoke,二者不同,一个是功能增强(需用户补全),一个是反射调用(直接使用)。然而实际中,是通过程序生成代理类,而不由我们自己编写,通过程序生成字节码后,再通过类加载器和反射调用进行操作。
反射优化:为了解决反射性能低。在多次反射调用一个方法后,生成一个代理类,以进行正常调用。
基于cglib方式代理:
限制:代理是子类型,目标是父类型,此时就不能对final的方法进行增强。
具体操作:Enhancer.create(目标对象,方法行为封装),其方法行为封装里又有intercep方法(代理类对象,方法对象,参数,代理方法对象),此时可以在其中使用三种方法进行功能增强,第一种:method.invoke(使用反射),第二种:methodProxy.invoke(没有使用反射,需要目标对象),第三种:methodProxy.invokeSuper(没有使用反射,不需要目标对象,使用代理对象)。
内部实现:继承目标对象,思路都一样,只是关于方法行为包装用法不一样。其特殊的第四个参数代理方法对象,创建方法为Method.create(目标类型,代理类型,方法参数描述,增强方法名,原始功能方法名。
反射优化:生成另外两个代理类,继承FastClass类,一个是原始功能方法的代理类,一个是增强功能的代理类。当调用methodProxy.invoke或methodProxy.invokeSuper时间接调用了fastClass的invoke方法,其根据方法签名信息(方法名和参数)获得索引,通过索引调用正确的方法。一个代理类包含多个方法,一来就生成。
注意:在jdk>=9 如果反射调用jdk中方法,非法访问
jdk<=8 不会有问题
jdk>8,运行时添加 –add-opens java.base/java.lang=ALL-UNNAMED(还可以在目标对象重写调用方法)
比Aspect更基本、粒度更小的切面Advisor:
特殊之处:只包含一个通知和一个切点。
编码实现:使用AspectJExpressionPointcut实现切点对象,为其设置切点表达式(其中切点表达式除了常规写法外,还有“@annotation(注解全类名)”能够检查方法上是否加了对应的注解,但其无法检查类上以及实现的接口上的注解,如@Transactional注解,此时可通过实现MethodMatcher接口,例使用一个StaticMethodMatcherPointcut抽象类,补全matches方法逻辑,要找注解信息可以使用反射也可以使用工具类MergedAnnotations.from方法,获得的注解信息再使用其isprsent方法检查是否出现该注解;另外有matches方法可以判断切点表达式是否与指定方法匹配),接着使用MethodInterceptor(与cglib的方法行为包装名相同,包不同)实现通知对象,切面使用DefaultPointcutAdvisor实现切面对象指定通知和切点,接着为其创建代理,使用ProxyFactory并为其设置切面(addAdvisor)和目标对象(setTarget),最后getProxy得到代理对象。
内部流程:后处理器在适当时机调用其wrapIfNecessary为有切面的创建代理,而在该方法内又调用一个findEligibleAdvisors返回指定目标的一个切面集合。内部发现如果是低级切面将直接加入集合,高级切面转换成低级切面后加入集合(内部是首先通过获得方法,解析方法上的相应的(isAnnotationPresent(注解名))注解(getAnnotation(注解名).value())获得其切点表达式,为对应注解创建对应通知,如:AspectJAroundAdvice,AspectJMethodBeforeAdvice,AspectJAfterReturningAdvice,AspectJAfterThrowingAdvice,其中需要指定方法对象、切点对象、切点工厂,切点工厂AspectInstanceFactory的实现类SingletonAspcetInstanceFactory(指定如何创建工厂,如new Aspect()))。接着为解析后的通知使用proxyFactory创建代理对象,再使用它的getInterceptorsAndDynamicInterceptionAdvice将其他通知转换成环绕通知,最后再创建一个调用链对象,进行调用所有切面功能增强。但某些通知内部需要使用到该调用链对象(ReflectiveMethodInvocation),所以还要在所有通知最外层加一个环绕通知,准备将调用链对象放入到当前线程为proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE)。
通知间的转换:除环绕通知外所有解析后的通知都将被适配器转换MethodInterceptor(环绕通知)的子接口,以便实现功能调用,为什么不直接解析为MethodInterceptor接口,大概率是历史原因,扩展的功能接口不一致使用适配器转换。
调用链的执行:实现MethodInvocation接口,在其中使用责任链模式配合环绕通知对象进行递归调用。
执行顺序:@Aspect切面可以在类上加@Order注解表示顺序,而以编码实现的Advisor只能通过切面对象的setOrder方法表示顺序。
动态通知调用:因为切点表达式需要参数绑定称之为动态的,而其相较于静态通知需要切点对象,而静态不需要。
代理实现选择:
在ProxyFactory的成员变量ProxyConfig对象proxyTargetClass属性
第一种:proxyTargetClass=false,目标实现了接口,使用jdk实现
第二种:proxyTargetClass=false,目标没有实现接口,使用cglib
第三种:proxyTargetClass=true,总是使用cglib实现
但注意:实现了接口,需要手动告诉工厂,如factory.setInterfaces(target.getClass().getInterfaces())。
另外不使用代理的增强:
第一种:改写class,AspectJ编译器读切面进行增强,与spring无关。
导入maven坐标,在通知类上标注@Aspect
注意:有时IDEA会走自己的javac编译器,使用maven的生命周期解决,且此时能够实现代理aop不能改写static的方法的问题。
第二种:类加载阶段修改class字节码。
具体操作:运行时需要在VM options里加入 -javaagent:C:/Users?manyh/.m2/repository/org/aspectj/aspecjweaver/1.9.7/aspectjweaver-1.9.7.jar
注意:此时突破了代理实现aop不能实现一个方法内部调用另一个方法的功能增强(因为目标对象是this调用,不会走代理)。
Spring事务
特别之处:除了数据层能开事务,还能在业务层开事务。
一个接口:PlatformTransactionManager
一个实现:DataSourceTransactionManager(使用jdbc事务,而Mybatis也是jdbc事务)
数据层操作:当执行数据层操作默认开启一个事务。
事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法。
事务协调员:加入事务方,在Spring中通常代指数据层方法,也可以是业务层方法。
注意:Mabatis和sqlsessionFactoryBean的数据源要和DataSourceTransactionManager的一样,才能进行统一管理!同时默认ERROR和运行时异常回滚,其他异常都不回滚,通过添加异常设置。
事务相关配置:
事务传播行为:在@Transactional中配置propagation=propagation.xx
Spring容器体系
ConfigurableApplicationContext容器继承体系图:
MessageSource:处理国际化资源的能力(翻译国家语言)(getMessage方法)
文件位置如下:
ResourcePatternResolver:对通配符匹配资源能力。(getResources方法)
ApplicationEventPublisher:发布事件对象。(publishEvent)
事件:继承ApplicationEvent类(指定事件源)
监听器:@EventListener
方法(事件){}
EnvironmentCapble:读取环境信息(环境变量,配置文件等信息)(getProperty方法)
BeanFactory:ApplicationContext的成员变量,表面上只有getBean功能,实际上控制反转、基本依赖注入、直至生命周期各种功能都由其实现类提供(DefaultListableBeanFactory(最重要))。
bean的定义
注册bean
DefaultListableBeanFactory容器继承体系图:
(DefualtSingletonBeanRegistry:管理单例Bean,所以的bean都放在其成员singletonObjects中)。
(DefualtListableBeanFactory:其成员resolvableDependencies存放所有特殊类型,如applicationContext,这些特殊类型在容器refresh初始化时preparedBeanFactory进行添加)
扩展功能
实现思路:为干净的容器添加后处理器(即执行后处理器)。
添加后处理器:调用静态工具类添加5个常用后处理器
注意:此时只是后处理器添加进入或存在于beanFactory。
调用beanFactory后处理器:执行后处理器(例获取beanFactory内置的后处理器并执行)
添加bean后处理器:将后处理器添加进入beanFactory,当getBean时进入bean的生命周期,回调后处理器。
注意:此时建立beanFactory与后处理的联系,表示创建bean时需要哪些后处理器。
注意:开始只是保存bean的描述信息在beanFactory中,只有用到时才实例化,延迟加载。但也可以调用方法提前准备好所有单例,如下:
小结
解决同类型后处理器解析先后的问题:
例:AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor
对应的@Autowired和@Resource同时使用时优先解析@Autowired,通过比较器对其进行排序,由order进行控制:
后处理器:
常见bean后处理器:
AutowiredAnnotationBeanPostProcessor @Autowired @Value(均依赖注入阶段执行)
内部实现:执行了后处理器的postProcessProperties方法,内部是通过findAutowiringMetadata方法先找标注@Autowired了的成员和方法,接着获得的meta调用Inject方法执行依赖注入,Inject方法内部是使用反射得到成员和方法封装成DependancyDescriptor,再调用beanFactory的doResovleDependcy方法找到其要注入的bean,最后反射注入。
注意:关于@Value:beanFactory的AutowireCandidateResolver默认不能解析值注入,通过换一个实现,例:context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver) (其中如果想要获取其内容,可以通过该resovler的getSuggestedValue获取值,而其中如果有环境资源变量之类需要解析,则需要通过容器的getEnvironment().resolvePlaceholders()方法解析指定内容,最后需要类型转换的值可以通过容器的getBeanFactory().getTypeConverter().convertIfNecessary()转换指定值和dependencyType())。同时@Value不止可以进行值注入也能进行依赖注入,它需要配合springEL使用,写法为#{SpEL},例#{@bean}, 通过容器的getBeanFactory().getBeanFactory().getBeanExpressionResovler().evaluate()解析指定值的springEL。由此可以知道@Value的解析流程为先解析原始括号内容,接着解析资源占用符${},最后解析springEL#{},再进行类型转换,并且其流程。关于@Autowired:注入方式多样,除了根据成员变量类型注入,还可以根据参数类型注入(但与成员变量注入不同的是,它需要一个MethodParameter封装指定方法名和参数索引),还可以通过内层嵌套泛型Optional<?>的方式注入(判断是否是嵌套类型后,通过dependencyDescriptor的increaseNestingLevel将此dependencyDescriptor转换为内层的,此时就可以找到对象了,找到对象后通过Optional.ofNullable()方法封装指定对象),还可以通过ObjectFactory<?>的方式注入(也是先将dependencyDescriptor转换成内层的,但此时给成员注入的是ObjectFactory工厂对象,重写其对象中的getObject方法返回产品对象,当需要该对象的时候再创建),还有一种方式是使用@Lazy注解为该对象生成代理(此时使用解析@Value内容的ContextAnnotationAutowireCandidateResolver,解析成员变量和方法参数的@Lazy注解,再使用该resovler的getLazyResolutionProxyIfNecessary方法生成指定depencyDescriptor的代理对象)。
关于doDependencyDescriptor:第一种查找类型(变量为数组):判断类型是否是数组后,使用dependencyDescriptor的getDependencyType().getComponentType()获得数组元素类型,再使用一个工具类BeanFactoryUtils的beanNamesForTypeIncludingAncestors根据指定类型在所有容器里找该类型bean的名字,遍历得到bean,此时可以使用dependencyDescriptor的resovleCandidate方法返回指定名字的bean(内部使用工厂的getBean方法),最后使用转换器类型转换。第二种(变量为集合):此时使用dependencyDescriptor的getRosolvableType(解析后的类型)的getGerneric()(不传索引或名字为第一个),获得泛型类型,接下来的步骤与第一种一样,不同之处是其不需进行类型转换。第三种(特殊类型非bean):通过工厂的resolvableDepencies.get获得特殊类型对象map,最后通过遍历集合,并通过工厂的特殊类型是否能赋值给dependencyDescriptor.getDependencyType()从而判断是否是子类或该类,这样就找到了对象。第四种(泛型精准匹配子类bean):与第一种一样根据类型找到bean的名字,再通过工厂的getMergedBeanDefinition(存有关于bean泛型的信息),最后通过处理的@Value和@Lazy的ContextAnnotationAutowireCandidateResolver的isAutowireCandidate (new BeanDifinitionHolder)处理对比beanDefinition和dependencyDescriptor的泛型是否匹配,最后根据名字获得bean。第五种(处理@Qualifier):与第四种相同,因为isAutowireCandidate还有解析@Qualifier的功能。第六种(处理@Primary注解):使用beanFactory.getMergedBeanDefinition().isPrimary()获得指定bean的名字的primary布尔值。
CommonAnnotationBeanPostProcessor @Resource(依赖注入)@PostConstruct(初始化前)@PreDestroy(销毁前)
ConfigurationPropertiesBindingPostProcessor.register(beanFactory)@ConfigurationProperties(SpringBoot中的一个注解,资源绑定对象)
注意:前两个后处理器是调用容器的registerBean方法注册的,最后一个较为特殊。
Spring内置依赖注入及初始化接口(Aware和InitializingBean):
BeanNameAware 注入bean的名字(Autowired不能注入名字)
BeanFactoryAware 注入BeanFactory容器
ApplicationContextAware 注入ApplicationContext容器
EmbeddedValueResovlerAware 解析${}
InitializingBean 等同于@PostConstruct
注意:虽然Aware和@Autowired是相同功能,Aware是内置功能,不会失效;@Autowired是扩展功能,其在配置类里包含BeanFactoryPostProcessor工厂方法的情况下,会导致原本流程执行BeanFactoryPostFactory->注册BeanPostProcessor->创建和初始化->依赖注入扩展->初始化扩展->执行Aware及InitializingBean->创建成功,变为执行第一步时需要使用配置类的工厂导致配置类提前创建,而配置类的那些BeanPostProcessor还没准备好,所以使得配置类里的@Au towired和@POSTConstruct失效。
AnnotationAwareAspectJAutoProxyCreator
第一个作用:找到容器里所有高级切面和低级切面,并将高级切面转换成低级切面
第二个作用:自动为切面创建代理
调用时机:创建->()依赖注入->初始化()括号处为调用时机。无双向依赖(也称循环依赖)时,调用时机为初始化后,创建代理对象;但在双向依赖时在依赖注入前被调用,提前创建代理对象,为依赖一方提供代理对象,等到依赖一方已经初始化完毕后才进行依赖注入和初始化。但是依赖和初始化阶段不应该被增强,依然应该是作用于原始对象(因为代理对象和目标对象是两个对象,二者不共享数据。且目标对象在spring容器里不存在,spring容器单例池里只存代理对象,如果想要通过代理对象得到目标对象,如下操作:proxy instance of Advised advised,再通过该接口的getTargetSource().getTarget()获得目标对象)。
常见beanFactory后处理器:
ConfigurationClassPostProcessor @ComponentScan@Bean@Import@ImportResource
注意:编写后处理器时实现BeanDefinitionRegistryPostProcessor接口传递工厂BeanDefinitionRegistry(可以使用registerBeanDefinition方法,不用强转成DefaultListableBeanFactory);而另一个实现BeanFactoryPostProcessor接口,传递过来的工厂是configurableListableBeanFactory(没有我们需要的方法,需要强转)。
内部实现:@ComponentScan内部通过AnnotationUtils的findAnnotation查找指定类上有没有加注解,接着获得ComponentScan注解的扫描包后,对其进行字符串拼接,因为有通配符,所以使用容器的getResource方法解析指定路径,在遍历资源是否加了@Component注解时再使用CachingMetadataReaderFactory的getMetadataReader(读取类的元信息,不走类加载效率比反射高)其又有getAnnotationMetadata得到注解元信息,其中值得注意的是其hasAnnotation(只判断是否加了@Component),hasMetadataAnnotation(判断是否加了@Component派生注解。再通过beanDefinitionBuilder创建bean定义,此时通过AnnotationBeanNameGenerator的generateBeanName根据beanDefination和beanFactory分析定义生成bean的名字。最后可以通过实现BeanFactoryPostProcessor接口并重写postProcessBeanFactory方法(在容器调用refresh方法时回调该方法,从而回调我们手造的后处理器)写我们自己的后处理器。重写后由于方法传递的工厂不适合当前调用的registerBeanDefinition方法所以使用configurableListableBeanFactory instanceof DefaultListableBeanFactory beanFactory转换一下(当然也可实现另一个接口),另外由于解析通配符的getResource方法会使用到容器,但在内部并没有容器,所以换一个实现PathMatchingResourcePatternResovler方法调用getResource,最后将后处理器注册到容器中完毕)。@Bean:不再使用创建普通类bean定义的方式,使用工厂方法的方式,与ComponentScan一样获取指定类的注解元信息后并通过getAnnotationMethod获取注解标注的方法,遍历方法注册到bean工厂中,其中获取beanDefinition时不再指定类名,调用BeanDefinitionBuilder的setFactoryMethodOnBean方法指定方法名和工厂名(因为工厂方法需要从工厂对象得到),其中工厂方法中带有参数注入的,需调用BeanDefinitionBuilder的setAutowiredMode方法设定自动装配模式为构造器装配(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR),另外想要获得@Bean中注有的内容,通过methodMetadata的getAnnotationAttributes方法获取指定注解的指定类型的内容,并通过BeanDefinitionBuilder的setInitMethodName之类为其设定内容。
注意:beanDefinition指定的类名是最终作为产品的类型,另外一个配置类往往被视作一个工厂,而其中@Bean标注是其工厂方法。
MapperScannerConfigurer @MapperScanner(扫描mybatis的mapper接口)(扫描包要为beanDefinition设置属性)
内部实现:编写后处理器时实现BeanDefinitionRegistryPostProcessor接口。通过配置类设置(@Bean)MapperFactoryBean工厂方法,通过MapperFactoryBean指定接口并为工厂设置sqlSessionFactory(由形参自动注入),最后返回一个工厂。可以一个个添加,也可以扫描。扫描:首先如ComponentScan一样先扫描并获取到类元信息getClassMetadata,通过其isInterface判断其是否是接口,是接口则为其设置beanDefinition,但要注意此时beanDefinition要指定类名,还要指定构造参数即为接口名,并且此时还要指定自动装配模式为AbstractBeanDefinition. AUTOWIRE_BY_TYPE(因为当mapperfactory需要使用到sqlSessionfactory时按类型在beanFactory中找),最后注册到beanFactory。
注意:在遍历资源时,使用generateBeanName按照MapperFactorBean的beanDefinition生成名字会出现前一个被后一个覆盖,所以再创建一个专门只用来生成名字的beanDefinition并让其根据接口定义生成,则其不会再发生覆盖,最后使用(小结:另外一个registerBean只指定类名)registerBeanDefinition(name(第二个专门生成名字的),bd(第一个beanDefinition))。
Application容器的四种创建方式
第一种:XML配置(ClassPathXmlApplicationContext)指定类路径
第二种:XML配置(FileSystemXmlApplicationContext)指定磁盘路径
内部通过读取xml文件bean定义来注册bean
第三种:Java配置类(AnnotationConfigApplicationContext)指定配置类,默认加了5个常用后处理器。
第四类:Java配置类(AnnotationConfigServletWebServerApplicationContext),用于web,指定配置类。(加好了常用的后处理器)
容器种类:GenericApplicationContext(干净容器)其refresh方法执行beanFactory后处理器,添加bean后处理器,初始化所有单例 其registerBean方法注册bean。
事件
具体实现:定义事件对象(继承ApplicationEvent,调用父类构造方法),使用ApplicationEventPublisher(applicationContext的一个实现)的publishEvent方法发布事件,定义监听器处理事件(一种方式是实现ApplicationListener接口,重写onApplicationEvent方法;另一种实现是使用@EventListener注解表示该方法是监听器方法,并且方法形参要求是事件对象),如果要使用多线程异步处理事件,需要定义线程池bean(ThreadPoolTaskExecutor类型),并且需要设置发布事件的方式为线程池(通过定义SimpleApplicationEvent Multicaster为其设置线程池对象,线程池对象从形参注入)。
关于@EventListener:通过扫描所有bean的方法是否标注注解,在其中创建一个ApplicationListener(重写其onApplicationEvent方法,过程中需要将事件类型进行比对,method.getPatameterTypes[0].isAssignableFrom(event.getClass())),然后将这个ApplicationListener加入到容器(context.addApplicationListener),最后将该过程封装为一个后处理器,实现SmartInitializingSingleton接口的afterSingletonsInstantiated(该方法会在所有单例对象实例化后被回调)。
关于SimpleApplicationEventMulticaster:实现其父类接口ApplicationEventMulticaster,其中重要的两个方法为addApplicationListenerBean(收集所有监听器,并加入ApplicationListener集合)、multicastEvent(发布事件,即调用所有监听器的onApplicationEvent方法),其中监听器事件类型通过ResolvableType.forClass(listener.getClass()).getInterfaces()[0].getGerneric()获得,由于ApplicationListener接口没有提供判断支持类型方法,所有将listener对象封装为其子类实现GernericApplicationListener(其supportsEventType方法判断支持事件类型,使用isAssignableFrom方法,另一方法为onApplicationEvent处理事件,从这个方法可以知道java总是一层包一层),最后如果要使用线程池发布事件则从形参传递参数,在GernericApplicationListener的onApplicationEvent中在线程池的submit中调用onApplicationEvent。
自动配置
解决问题:自动配置通用的第三方bean,减少重复操作,简化开发
实现原理:项目配置类里@Import只需导入配合该注解使用的选择器类(实现ImportSelector,返回第三方配置类名,当然此时可以使用配置文件的形式读取,方便管理,(META-INF目录下的spring.factories文件,可以使用\换行)可以使用spring提供的工具SpringFactoriesLoader.LoadFactoryNames,指定key(key为全类名,内部类为”包$类”,外部类为”包.类”)),指定的类加载器可以为null,最终只需将集合转为数组toArray(new String[0])。
注意:为了解决第三方bean与项目bean重名问题,实现的接口变为DefferredImportSelector,推迟导入,并在第三方bean上加入注解@ConditionalOnMissingBean。另外要想让springBoot自动加载我们定义的第三方bean,只需要在SpringFactory的配置文件里将前缀名改为EnableAutoConfiguration的全类名,同时配置类上的@Import导入的Selector直接用SpringBoot写好的AutoConfigurationImportSelector。
自动配置类:
AopAutoConfiguration:通过静态内部类条件判断的形式为其添加代理创建器的bean。
DataSourceAutoConfiguration
MybatisAutoConfiguraion
DataSourceTranscationManagerAutoConfiguration
TranscationAutoConfiguration
关于MVC:ServletWebServerFactoryAutoConfiguration、DispatcherServletAutoConfiguration、WebMvcAutoConfiguration、ErrorMvcAutoConfiguration。
类型转换
作用:用来转换数据类型。
两套底层接口(猜想是历史遗留问题,为了兼容没有丢弃)
底层第一套转换接口与实现:
Printer:把其他类型转为String类型。
Parser:把String转为其他类型。
Formatter:综合Printer和Parser的功能。
Converter:把任意类型转为另一任意类型。
Converters:一个集合,存放Printer、Parser、Converter经过适配转换成的GenericConverter,方便调用。
FormattingConversionService:利用以上转换接口进行类型转换。
底层第二套转换接口(jdk自带,非spring提供):
PropertyEditor:把String类型转为其他类型。
PropertyEditorRegistry:可以注册多个PropertyEditor对象。
转换:若要与第一套接口进行转换,可以通过FormatterPropertyEditorAdapter进行适配。
一套高层转换接口(间接调用底层接口进行实现转换)
转换接口查找顺序:PropertyEditor自定义转换器(@InitBinder添加的属于这种)->ConversionService接口->默认PropertyEditor转换->一些特殊处理。
SimpleTypeConverter:仅做类型转换。
BeanWrapperImpl:为bean属性赋值(由于创建bean是使用反射不知道有多少属性),并且当需要类型转换(反射走Property(getter、setter方法))。
DirectFieldAccessor:功能一样,但其反射走Field。
ServletRequestDataBinder:将数据与bean属性进行绑定,并且当需要类型转换时,根据布尔变量directFieldAccess选择使用Property或Filed,在这过程中还可以校验和获取校验结果功能。它是DataBinder的子类,适用于web环境,同时对应的数据对象使用ServletRequestParameterPropertyValues对象。
注意:ServletRequestDataBinder比较特殊需要使用DataBinder(绑定器)对象和MutablePropertyValues对象(数据),调用绑定器的bind方法将俩绑定起来,另外如果调用绑定器的initDirectFieldAccess方法即选择使用Field。而其余三个实现仅只要与其名相同的对象,并为其赋值即可。
绑定器工厂(方便添加选项功能):其中一个工厂ServletRequestDataBinderFactory,使用其createBinder创建绑定器。如果要扩展类型转换器,第一种方式是使用@InitBinder:使用@InitBinder注解定义添加类型转换器的方法后,用InvocableHandlerMethod方法封装该转换器方法,最后设置到该工厂的第一个参数中(使用PropertyEditor接口)。第二种方式是使用ConversionService:使用FormattingConversionService对象的addFormatter方法添加自定义类型转换器,再使用ConfigurableWebBindingInitializer对象的setConversionService方法添加FormattingConversionService对象,最后再将ConfigurableWebBindingInitializer对象设置到该工厂的第二个参数中(使用ConversionService接口)。如果工厂的两种方式都实现了,将会按照转换接口查找顺序来。
注意:可以使用FormattingConversionService的子类DefaultFormattingConversionService提供了默认的转换解析(比如@DatePattern注解使用的转换陌生格式的日期),另外在springBoot环境下还有一个子类ApplicationConversionService也提供了一些强大的功能。
其他技术
Lombok(坐标lombok,快速开发实体类,·@Data包括@Getter、@Setter等但不含构造方法)
其他知识
alibaba的arthas工具可以调试程序,包括反编译(指定类名,且程序在运行,不能结束,使用System.in.read等待输入挂着程序)等等。
ASM Bytecode Outline:IDEA插件,将现有源码转换成asm代码,asm代码复制出来直接生成字节码(不能支持高版本)。
IDEA中将target目录中的java文件拖到内容区可以实现反编译。
运行时异常可以直接抛出(RuntimeException | Error),检查异常转换成运行时异常抛出(new UndeclaredThrowableException(异常))。
可以使用类加载器直接加载使用二进制字节码,其中重写ClassLoader的findClass查找一个类,返回defineClass方法根据字节数组生成类对象,此时使用loadClass方法加载类对象,最后就可以创建实例了。
接口只有一个方法时,被称作函数式接口,可以进行lamda简化。
如果要调用第三方受保护的方法,第一种办法是写一个子类继承,第二种办法是反射调用,第三种办法是将包放到第三方包中。
关于泛型:jdk api:使用class对象的getGenericSuperClass方法获得带有父对象的泛型信息,若只想获得泛型信息,先判断能否类型转换再转换,type instanceof ParameterizedType parameterizedType,这时再通过该子类型的getActualTypeArguments方法获得泛型类型。spring api:使用工具类GernericTypeResolver的resolveTypeArguements方法获得泛型类型。
代码设计
模板方法:取出代码中固定部分抽象为接口,其余通过遍历搜索变化不确定的代码完成通用性。
构造成员接口:将不能确定的逻辑且需要外部给定的内容封装为一个接口,并且将其作为有参构造方法。这个接口也需一定的参数规范,即用户实现接口方法,内部类即为可用,从而内部类可以使用接口方法进行根据不同类型内容成为一个完整的类。此时用户就可以通过调用方法实现自己的需求。例:jdk代理。
静态代码块:常用不变可复用代码块将其作为静态代码块,编码每个方法或对象里都重新创建。
适配器模式:将一套接口转换成另一套接口,以便实现功能调用,中间的转换对象为适配器对象。
责任链模式:递归中,不在该方法或对象中调用当前方法,而是调用该执行链上的下一个对象的方法,下一个对象的方法中又将回调递归中的方法。例:过滤器,拦截器,aop增强执行链。
观察者设计模式:异步执行方法,观察到某一事件发生后被调用,例:事件的发布和监听。