Spring

1 Spring

1.1 基础理解

  • 官方文档:https://spring.io spring官方文档

  • 定义:

    • 轻量级JavaEE解决方案开源框架;整合众多优秀设计模式;用来帮助管理JavaBean(除Entity,它由持久层框架管理)

      • 解决方案即分层处理各个问题

      • Mybatis等只专注处理其中一层;灵活的Spring不要求必须完全使用自己,反而可以整合Mybatis,及第三方类库

    • 反转控制(Inverse of Control)和面向切面编程(Aspect Oriented Programming)为内核,提供了展现层Spring MVC、持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术

  • 框架体系:

    1. 实体

      1. Entity:与数据库的表对应

      2. Bo:business object即业务对象,是对业务逻辑实现过程中需要用到的属性和方法的封装

      3. Vo:value object即值对象,常用于控制层与视图层数据传递的封装

      4. Po:persistent object即持久层数据封装,可理解为数据库中数据的一条记录

      5. Dto:data transfer object即数据传输对象

      6. Pojo:简单无规则对象

    2. Dao:数据库数据的CRUD;事务管理层

    3. Service / ServiceImpl:业务层

    4. Controller:视图层,与前端交互

  • Spring启动流程:

    • (标签< load-on-startup>对应)类的onStartup()方法让Tomcat加载WebApplicationInitializer(开发使用最底层的有更多实现内容的AbstractAnnotationConfigDispatcherServletInitializer)完成容器的启动和初始化,包括注册MVC的Servlet,Filter,Listener三大组件,及创建根容器WebApplicationContext并将配置类保存到容器中

    • listener:创建监听器;< listener>< listener-class>ContextLoaderListner;< context-param>设置配置文件或配置类(即上下文参数)的位置或new ContextLoaderListner

  • Sring组成及核心:

  • 优点:

    • 低侵入式设计,代码的污染极低

    • 独立于各种应用服务器,基于Spring框架的应用,可以真正实现写一次后在任何地方调用的承诺

    • IoC:降低组件之间的耦合性即方便解耦,降低业务对象替换的复杂性即简化开发

    • AOP:支持将一些通用任务如安全、事务、日志等进行集中式管理,以提高复用

    • Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化底层的数据库访问

    • Spring高度开放,并不强制应用完全依赖Spring,开发者可自由选用Spring框架的部分或全部

  • Spring中用到的设计模式:工厂模式(BeanFactory)、单例模式(Bean作用域)、适配器(Adapter)、装饰器(BeanWrapper)、(动态)代理、观察者(listener,event,multicast)、策略、模板方法

  • Spring流程图:

1.2 Bean

  • 传统EJB(Enterprise企业级 Java Bean):重量级框架

    • 运行环境苛刻:运行在EJB容器中;服务器都包含了Servlet引擎,Oracle的Weblogic或IBM的WebSphere还包括了EJB容器,收费,而免费的Tomcat没有EJB容器

    • 代码(在服务器间的)移植性差

  • Spring Bean轻量级,没有EJB存在的问题;容器作为超级大工厂,由BeanFactory负责创建、管理所有的Java对象;属性都是私有,对外提供访问的getter()和setter()方便扩展

    • 对于开发者来说,使用Spring框架主要做两件事:①开发Bean;②配置Bean。对于Spring框架来说,框架要做的就是根据配置文件来创建Bean实例,并调用Bean实例的方法完成IoC

    • 与普通java对象的区别:表面区别是一个从配置或注解中获取,因为自动注入而有值,如@Component标注Service,即Spring为我们创建了这个Bean,而Service中@Autowired一个Pojo,这个Pojo有值;而普通对象是通过new,可能无值

    • Bean的定义方式:XML,Annotation,配置类,其他

    • FactoryBean(工厂Bean):特殊Bean,Spring提供的一种灵活创建Bean的方式,实现FactoryBean中的接口

      @Component
      public class MyFactoryBean implements FactoryBean {
          // 返回最终Bean对象;当与返回Bean对象不符时异常BeansNotOfRequiredTypeException
          // 如果想得到FactoryBean对象,则beanName前面加上&符号
          @Override
          public Object getObject() throws Exception {
              return new User();
          }
          // 定义数据库连接
          // @Override
          // public Connection getObject() {
               // Class.forName("com.mysql.jdbc.Driver");
               // Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/..","root","123");
               // return conn;
           // }
          @Override
          public Class<?> getObjectType() {
              return User.class;
          }
          @Override
          puvlic boolean isSingleton() {
              return ture;
          }
      }
  • Spring容器本身没有提供Bean的线程安全策略;即Bean不是线程安全的;但对于原型Bean,每次获取Bean时都创建新对象,即线程间不共享,不存在线程安全问题;对于单例Bean,所有线程共享一个Bean,存在竞争资源关系,若此Bean无状态即不存储数据,线程只对其做查询操作等,则线程安全,如Service ,Dao ,Controller 大都无状态

  • BeanDefinition:Bean定义对象,即容器以此来创建Bean ,包含的属性用来描述Bean@Component系列注解、@Bean、< bean/>都被解析为BeanDefinition对象;@Component标注普通Bean,@Reposity标注Dao组件,@Service标注业务逻辑组件,@Controller标注web组件;它们都默认省略value="beanId"属性,所以同一种类可以定义为多个Bean(区别于单例);name属性或< alias>标签取别名;@RequestScope、@SessionScope、@ApplicationScope定义Bean的作用域;实现类GenericBeanDefinition;属性包括:

    • beanClass:Bean的类型,如User.class

    • scope:Bean的作用域

    • isLazy:是否需要懒加载(针对单例);懒加载机制让Bean在第一次getBean()时生成,否则会提前在容器启动过程中生成

    • dependsOn:依赖;Bean被创建之前,它所依赖的需要先创建好

    • primary:标注当前Bean为主Bean;类可以创建多个对象,在依赖注入时需要判断是否存在主Bean,有则直接注入

    • initMethodName:Bean的初始化方法,自定义的Bean初始化逻辑;自定义方法而不是实现Spring提供的方法,则避免了框架的侵入;两种方法都存在时,先调用Spring的,再执行自定义的

    • 其他:constructorArgumentValues参数值,parentName父类名,factoryBeanName工厂名,propertyValues属性,destroyMethodName销毁方法名,autowireMode注入类型,factoryMehtodName工厂方法名

  • BeanFacotry:Bean工厂,Spring容器,所有BeanDefinition对象的组合;在容器中利用BeanDifinition创建和管理Bean;核心简单容器,提供基本的Ioc和DI功能,适合测试和非生产环境 ;Spring内部核心接口,核心简单容器,提供基本的Ioc和DI功能,适合测试和非生产环境,一般不供开发者使用;在加载配置文件时未创建对象,在获取或使用时才创建,即getBean()时通过名字检索容器中管理的Bean;提供扩展和更多功能的核心接口和实现类有:

    • ApplicationContext/ ConfigurableApplicationContext:

    • ListableBeanFactory / DefaultListableBeanFactory:支持Bean的单例、别名、类型转化、后置处理、自动装配;Spring默认以它为IoC容器;核心属性ConcurrentHashMap <String, BeanDifinition> beanDifinitionMap用于存储BeanDifinition,key为beanId

      // 源码
      public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
      	// ...
          private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap(16);
          private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap(256);
          private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap(64);
          private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap(64);
          private volatile List<String> beanDefinitionNames = new ArrayList(256);
          private volatile Set<String> manualSingletonNames = new LinkedHashSet(16);
      	// ...
      }
    • ConfiguratbleBeanFactory:

    • AutowireCapableBeanFactory:

    • AbstractBeanFactory:

  • ApplicationContext应用上下文(核心API):BeanFactory的高级形态,Context模块的核心接口,扩展了BeanFactory的功能

    • 还继承的其他接口包括:

      • EnvironmentCapable:可获取操作系统和JVM的环境变量

      • ListableBeanFactory:可获取所有beanNames,某个类型的所有beanNames,判断某个beanName是否存在对应的BeanDifinition,统计BeanDifinition个数

      • HierarchicalBeanFactory:可获取父BeanFactory,判断某个name是否存在对应的Bean对象

      • MessageSource:i18n国际化

      • ApplicationEventPublisher:事件发布

      • ResourcePatternResolver:可加载并获取文件、图片等URL资源

    • 主要实现类:ApplicationContext接口采用工厂设计模式(所以主要作用是创建bean),屏蔽了实现的差异

      • ClassPathXmlApplicationContext:从类加载路径下搜索配置文件bean.xml,其中是< bean>的配置;适合非Web环境如main()和Junit中

        • 构造器中先读取配置文件信息并存储为字符串数组,比如期间会调用doResolvePlaceholders()做配置文件中${}的解析和替换;然后调用refresh()

        • refresh():容器的创建和初始化

          • prepareRefresh():容器创建之前的预先准备,如记录容器启动时间和标记

          • obtainFreshBeanFactory():创建BeanFactory即bean工厂,如果已存在则销毁;然后装载(加载并存储)BeanDefinition

            • refreshBeanFactory():调用createBeanFactory()返回DefaultListableBeanFactory即工厂创建

            • 工厂传入调用的loadBeanDeifinitions()中,用来创建XmlBeanDefinitionReader,用它初始化环境信息,资源加载器和entity解析器,然后去加载和解析Bean定义的xml配置文件(过程很复杂,包括但不限于判断文件前后缀,数据流化,Bean别名解析,解析自定义信息等)并封装为BeanDifiniton,解析结果是一个beanDifinition的话,BeanDifinitionReaderUtils.registerBeanDefinition()注册该Bean到容器中即beanDifinitionMap.put(beanName, beanDifinition);所有的beanNames还会保存到一个beanNames的List中

          • prepareBeanFactory():配置Bean工厂的标准上下文特性,如类加载器,后处理器等

          • postProcessBeanFactory():模板方法,装载BeanDefinition后的一些处理,如提供Bean工厂的一个入口

          • invokeBeanFactoryPostProcessors():提供对BeanDefinition的修改的入口

          • registerBeanPostProcessors():注册用于拦截Bean创建过程的BeanPostProcessors

          • 完成实现的接口的方法,如初始化MessageResouce,初始化事件等

          • finishBeanFactoryInitialization():完成容器的初始化;完成单例对象的实例化

            • preInstantiateSingletons():循环beanNames的List,判断其对应的Bean对象是否已经被实例化(调用getMergedLocalBeanDifinition()判断,即属性是否填充,是否合并了父类信息);如果没有实例化且是单例且没有懒加载,则调用getBean()完成实例化,保存到缓存中

          • finishRefresh():

        • 构造器执行完毕后,使用getBean():从缓存中查找需要返回的Bean

      • FileSystemXmlApplicationContext:从绝对路径下去搜索配置文件

      • AnnotationConfigApplicationContext:从配置类中搜索Bean

      • XmlWebApplicationContext:适合Web环境

    • 重量级;占内存但一个应用只创建一个工厂对象,被多线程并发访问但线程安全

  • ClassPathResource:获取配置文件资源;不同来源对应不同实现类;封装为Resource类型后传给读取器的构造器;getBean():通过名字检索容器中管理的Bean;使用BeanFactory时仅实例化Bean,而使用ApplicationContext时已经实例化完成,它在容器启动即new时实例化;带参数则检索时类型检查

  • Bean装载过程/生命周期:创建-初始化-使用-销毁

    1. Bean定义:容器扫描到类或加载配置文件后,解析文件得到BeanDefinition对象,而不是直接new对象,因为需要根据描述分析该Bean的配置,如Bean类型,是否懒加载等

      1. 方法执行链:refresh() -》refreshContext() -》finishBeanFactoryInitialization() -》 getBean() -》doGetBean() -》 getSingleton()和getObjectForBeanInstance()

      2. 方法主要工作:容器中产生一个用来存储BeanDefinition的Map<beanName, BeanDefinition>结构;如果map中不包括beanName,则尝试从parentBeanFatory中检测,也就没有后续方法操作;doGetBean()中提取校验和转换beanName,包括如果是别名则获取最终neanName,如果是FactoryBean则去掉特有的修饰符后取值,如&user取user;如果beanName是子Bean,则合并父Bean的相关属性;如果使用XML配置方式,则bean信息存储在GenericBeanDefinition中,而所有bean的后续处理都是针对RootBeanDefinition,此时需要转换

      3. getSingleton(beanName):依赖检查,并尝试从缓存(按照一二三级缓存顺序)中获取对象;因为创建单例时可能存在循环依赖,为了解决它,Spring不等bean创建完成,将其放入实例工厂ObjectFactory并曝光,在被依赖时直接使用

      4. getObjectForBeanInstance():bean的实例化;缓存中获取的bean是原始状态

    2. 实例化Bean:依据BeanDefinition,默认调用无参构造器(实际可能需要推断选出构造器)或方法上使用@PostConstruct,通过反射方式在堆中申请空间,创建对象并赋予默认值

    3. 配置属性:setter()的调用,自动依赖注入

    4. 检查Aware相关接口并设置相关需要引用的容器内部的依赖对象:主要调用invokeAwareMethod()方法,完成BeanName,BeanFacotry,BeanClassLoader对象的属性设置

    5. 实例传给Bean前处理器BeanPostProcessor进行前置处理工作(AOP)

    6. 初始化Bean:对其他属性的赋值和校验,如实现InitializingBean的afterPropertiesSet(),或xml中配置inti-method属性指向自定义方法,来手动做一些初始化(由Spring工厂自动调用方法);而后可能将实例传给Bean后处理器处理

    7. 实例传给Bean前处理器BeanPostProcessor进行后置处理工作(AOP),如生成代理对象

    8. 常见的Bean此时已就绪,驻留在上下文或单例池中供程序使用;getBean()获取;如果Bean经过AOP,则单例池中存放的是Bean的代理对象

    9. 销毁:工厂关闭即context.close()时,实现DisposableBean的destory(),或destory-method属性指向自定义销毁方法;或使用@PreDestroy销毁前的方法;销毁只适用单例

  • Bean作用域

    • singleton:默认单例,无论创建多少次都是同一个实例,实例化两个Bean对象,bean1 == bean2为true;使用ThreadLocal保证线程安全;加载配置文件时就会创建单例对象;针对单例模式,不需要在启动时立即加载Bean到容器中,可延迟加载(即等到调用getBean()时初始化):< bean id class lazy-init="true"/>或注解@Lazy(true)

      • 多次请求,但Spring容器里只实例化一个Bean,并保存到缓存的map中;后续请求先从缓存中查找,有则直接使用,无则再实例化

      • 为何Spring的Bean设置为单例:减少实例化(反射或cglib创建对象并分配内存)时的消耗;减少JVM垃圾回收;快速获取到Bean,第一次生成,后面都是从缓存中获取

      • 劣势:所有线程共享此Bean,如果此Bean是有状态的,则非线程安全,可能在并发场景下出现问题

    • prototype:singleton=”false”;原型、多例,每次获取Bean时,如调用getBean(),都将产生一个新的Bean实例,没有从缓存查找的过程;销毁代价比较大

    • request:Web环境每一次HTTP请求(每一次请求方法的调用)都创建一个实例

    • (global) session:Web环境每个全局的HTTP Session共享一个实例,不同session创建不同实例

    • 标注作用域:< scope=””>做< bean>标签属性或Bean上使用注解@Scope(“prototype”);常用更安全的类封装的字段常量,如ConfigurableBeanFactory.SCOPE_PROTOTYPE

    • request和session作用域的Bean有proxyMode属性,即设置代理并延迟加载:当此Bean注入到一个单例Bean时,单例已经初始化,但此Bean还不存在;委托相同功能的代理Bean解析和延迟加载,真正调用时才使用此Bean

      • Bean为接口,则值ScopedProxyMode.INTERFACES

      • Bean为类,则需要使用CGLIB生成代理类,值ScopedProxyMode.TARGET_CLASS;它对应aop命名空间中的标签< aop:scoped-proxy>,默认使用CGLIB创建目标类的代理,属性proxy-targer-class值为false时创建接口的代理

    • 协调作用域不同步的Bean:

      • 不同步现象:如当singleton作用域的Bean依赖于prototype作用域的Bean时产生,因为Spring容器初始化时,预初始化容器中所有的单例,而这些Bean依赖于prototype Bean,于是Spring会先创建prototypeBean,再创建singleton Bean,再将prototype Bean注入singleton Bean

      • 解决方法:

        • 放弃依赖注入: singleton作用域的Bean每次需要prototype作用域的Bean时,主动向容器请求新的Bean实例,即可保证每次注入的prototype Bean实例都是最新的实例

        • 利用方法注入: 推荐;方法注入通常使用lookup方法注入,该方法注入可以让Spring容器重写容器中Bean的抽象或具体方法,返回查找容器中其他Bean的结果,被查找的Bean通常是一个non-singleton Bean。Spring通过使用JDK动态代理或cglib库(Spring4.0的spring-core-xxx.jar包中已集成了cglib类库)修改客户端的二进制码,从而实现上述要求;使用lookup方法注入的两步:将调用者Bean的实现类定义为抽象类,并定义一个抽象方法来获取被依赖的Bean;在< bean…/ >元素中添加< lookup-method…/ >子元素让Spring为调用者Bean的实现类实现指定的抽象方法Notes;Spring会采用运行时动态增强的方式来实现<lookup-method.../>元素所指定的抽象方法,如果目标抽象类实现过接口,Spring会采用JDK动态代理来实现该抽象类和抽象方法;如果目标抽象类没有实现过接口,则采用cglib

  • BeanPostProcessor:Bean后处理器,Spring提供的一种扩展机制(AOP),对Bean进行加工;良好的扩展性是框架的必备;特殊的Bean接口;不对外提供服务,它甚至可以无须id属性;主要负责对容器中Bean进行后处理或额外加强,如为容器中的目标Bean生成代理,或在Bean实例创建成功之后对Bean实例进行进一步的增强处理。一旦注册了Bean后处理器,它就会自动启动,在Bean创建时自动工作;Bean后处理器必须实现BeanPostProcessor接口及方法:

    • InstantiationAwareBeanPostProcessor:子接口,提供postProcessBeforeInstantiation()实例化前置处理方法,postProcessAfterInstantiation()实例化后置处理方法,postProcessProperties()属性出入后处理方法

    • Object postProcessAfterinitialization(Object bean, String name) throws BeansException:(初始化)后置处理方法

    • Object postProcessBeforeInitialization(Object bean, String name) throws BeansException:(初始化)前置处理方法,参数1为目标Bean实例,参数2为该Bean的配置id

  • BeanFactoryPostProcessor:工厂后处理器:对IoC容器本身进行后处理或增强容器功能;postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法体对Spring容器进行的处理,如对容器进行自定义扩展,对BeanDefinition对象进行加工

  • 配置文件参数化:将需要频繁修改的字符串转义到小配置文件中,如数据库连接信息,DataSource的相关Bean信息全部配置到了ymal等文件中,便于修改和运维

    • Spring整合小配置文件:< context:property-placeholder location="classpath:/...">引入;配置文件放在resources目录下,项目编译后该文件就在classes目录下,即与java目录同级,用classpath指定

    • 使用${key}获取值

  • 复杂组件:不能直接通过new等简单创建的对象,或者< bean>等方式创建Bean时需要大量配置信息的,创建方式不灵活,便可使用编码方式定制创建Bean;如build()创建的流,jdbc创建Connection等;实例工厂方式创建:

    • 实现FctoryBean的方法:Spring根据id获取Bean标签信息,instanceof判断Bean类型,如果是实现了FactoryBean的,则Spring去调用自定义的getObject()生成实例,否则为普通的new实例;它隐藏了一些复杂的实例化细节;class指定的是实现FctoryBean的类,因此与平常通过id获取对象时不同,它获取出来的是实现方法里返回的Bean即FacoryBean#getObject()的返回代理对象;如果想获取该类,在id前加&,如ctx.getBean("&user")
    • XML方式:编写Factory,创建Bean并注册Factory;然后配置指定它为FactoryBean并指明方法

       <bean id="factory" class="..."></bean>
       <bean id="" factory-bean="factory" factory-method="getObject">

1.3 IoC

  • 原始做法: 调用者与被依赖对象硬编码耦合,不利于项目的升级与维护

    • 调用者主动new被依赖对象,调用被依赖对象的方法

    • 简单工厂模式: 调用者先找到被依赖对象的生产工厂,由工厂get()去获取被依赖对象,再调用被依赖对象的方法;没有最低限度降低耦合

  • 使用IoC:调用者无需主动获取被依赖对象,调用者只要被动接受Spring容器为调用者的成员变量赋值即可;主动变被动,即控制反转;从容器角度看,容器负责依赖对象的赋值,即依赖注入(dependency injection)

    • 控制:对成员变量赋值的控制权:代码,容器(配置文件或注解 + Spring工厂)

    • 反转:从耦合的代码转移到解耦的容器

    • 依赖倒置DI:通过IoC容器,将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。将类和类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描述,由IoC容器负责依赖类之间的创建、拼接、管理、获取等工作;如不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,更加专注于上层应用;通过框架或IoC容器在对象生成或初始化时注入数据或所依赖对象的引用并管理

    • 存储:容器采用三级缓存机制,map结构来存储对象

  • 底层原理:配置解析+工厂模式+反射 - 实现IoC容器

    1. 通过creatBeanFactory()创建Bean工厂

    2. 使用工厂循环创建Bean对象,由于Bean默认单例,所以先通过getBean()、doGetBean()从容器中查找,找不到再创建;解析xml文件,获取id等信息

    3. 通过createBean()、doCreateBean()以反射方式创建Bean对象,反射< bean>标签forName()得到Class,newInstance()实例化后得到对象;反射会调用无参构造器,包括私有;< property>标签等依赖注入同理;然后属性填充populateBean()

    4. 其他初始化操作initializiBean()

  • 核心机制:管理Bean:主要为Bean创建和依赖注入

  • 装配后默认beanId为全限定名+”#数字”,用作计数区分,区别于打印类时输出类名@地址

  • 不能装配原生数据类型;IoC是为了解耦,基本数据类型不存在耦合问题;注入的< property>列表构成的整个对象而不是注入单个< property>;但可以用@Value引入,用动态表达式从属性配置文件中获取单个值

  • 两种管理方式:

    • xml配置方式:

      • 前几行引入库/模式用来管理xml,如使用< bean>标签则需要spring-bean模式

      • Bean发现机制:< context:component-scan base-package="包列表或简化写为上层">命名空间规定扫描范围;< context:include/exclude -filter type=”” expression=”….*Dao” />

        • 可选属性use-default-filters="是否使用默认filter";< context:include-filter type="" expression="">标识自定义filter,即仅扫描expression满足type要求的;而exclude-filter不去扫描符合的

      • 组合:引入外部文件,< import resource="其他xml.xml>

      • < bean id="对象唯一标识,一般为类名首字母小写" class="类全路径" autowire=“byName / byType">标签标识对象:

        • 手动装配:< property>列表,标识注入的依赖,name为依赖名,value为属性值;ref为引用的beanId

        • 自动装配:autowire属性,省略< property>的手动装配;default-autowire对配置文件中所有的Bean起作用,autowire对当前Bean起作用;可取值:

          • no:默认值;不使用自动装配。Bean依赖必须通过ref元素定义。推荐不修改此值,显式配置合作者能够得到更清晰的依赖关系

          • byName:根据setter()名进行自动装配;Spring容器查找容器中全部Bean,找出其id与setter()名去掉set前缀并小写首字母后,同名的Bean来完成注入。如果没有找到匹配的Bean实例,则不进行任何注入

          • byType:根据setter()的形参的类型自动装配。Spring容器查找容器中的全部Bean,Bean类型与setter()的形参类型匹配,则自动注入这个Bean;如果找到多个这样的Bean,则抛出异常;如果没有找到这样的Bean,则什么都不会发生,setter()方法不会被调用

          • constructor:与byType类似,区别是用于自动匹配构造器的参数

          • autodetect:Spring容器根据Bean内部结构,自行决定使用constructor或byType策略。如果找到一个默认的构造函数,那么就会应用byType策略

        • 手动装配覆盖自动装配;装配第三方库中的Bean只能依靠手动,如SqlSessionFactoryBean,各种DataSource

      • 默认执行无参构造器,不灵活;自定义有参而未写无参构造器时,可能报错NoSuchMethodException

      • 排除Bean在自动装配之外:< beanId="" autowire-candidate="false" />;或< beans>中以模式字符串指定,如排除所有以abc结尾的Bean:< beans default-autowire-candidates="*abc" />

      • 常见的xml方式注入对象,如连接池:

        <!--使用context名称空间引入文件;value值一般都是外部属性配置文件如spring.properties中读取;-->
        <context:property-placeholder location="classpath:spring.properties"></context:property-placeholder>
         ​
        <!--配置德鲁伊数据库连接池;value值替换为动态表达式获取属性配置文件中的key,如${spring.druid.driverClass}-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <propertry name="url" value="jdbc:mysql://localhost:3306/DB"></propertry>
            <property name="username" value=""></property>
            <property name="password" value=""></property>
        </bean>
        <bean id="user" class="com...User" name="别名">
            <!-- 默认beanId为类名首字母小写,上面id配置可省;beanName为class后+#0序列 -->
        </bean>

      • 测试:

        @Test
        public void test() {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            DruidDataSource source = (DruidDataSource)context.getBean("dataSource"); // 参数为id或name值,区别是id唯一
            // source = context.getBean("dataSource", DruidDataSource.class);
            // source = context.getBean(DruidDataSource.class); 这种方法要求注入的bean唯一,配置文件中支持定义不同id,但class相同,因此需要注意
            String[] beanDefinitionNames = context.getBeanDefinitionNames(); // getBeanNamesForType(User.class)
            // containsBeanDefinition("user");containsBean();
        }

    • 注解方式:

      • Annotation是代码特殊标记;简化xml配置甚至零配置,但降低了依赖关系的清晰性和透明性,依靠源文件的属性名或属性类型装配,导致Bean与Bean之间的耦合降低到代码层次,不利于高层次解耦;需引入spring-aop依赖包

      • @注解名(key=value列表);使用在类、方法或属性上

      • Bean发现机制:@ComponentScan开启组件扫描;程序启动类或配置类上使用@ComponentScan(value="包");属性value或basePackages以字符串形式指定扫描基础包列表,类型不安全;或属性basePackageClasses={A.class,B.class}指定具体类或接口,安全

      • 自动注入:

        • @Autowired:byType方式;可用于setter()、普通方法、实例变量和构造器;常用于接口或父类。所以可能出现红色下划线而项目依然可运行;当符合的Bean实例有多个时,可能引起异常NoUniqueBeanDefinitionException;对Bean设置为首选@Primary,但有局限,在设计期间不确实谁是首选;或者@Autowired用于具体实现类

        • @Qualifier:byName方式;在多个实现类时配合@Autowired使用,可用value指定具体类;否则可能NoSuchBeanDefinitionException

        • @Resource:byType方式,也可用name属性设置为byName,位于javax.annotation包即java提供的注解

          • required属性为false设置Bean暂时处于未装配状态,否则若没有匹配的Bean,上下文在创建时抛异常;或有多个满足的Bean时抛出异常,而后进行null检查;可换为@Inject(来自javaDI规范)

      • 配置类@Configuration标注的类,替代xml配置文件;java显式配置,更解耦和无侵入,类型安全,重构友好:加载配置文件改为对应Context的实现类来加载配置类即参数为类.class

        • 配合@ComponentScan扫描Bean

        • 不扫描,而在类中定义Bean:创建返回Bean对象的方法且以@Bean标注,属性name的值或重命名该方法名为beanId;SpringBoot常用

        • 组合:@Import(配置类列表.class);@ImportResource(“classpath:配置文件.xml”);

        • 配置类PropertySourcePlaceholderConfigurer,用于支持动态表达式,等同xml中引入命名空间

      • 动态表达式-运行时值注入:改善直接value=””的硬编码:

        • 属性占位符:

          • 配置属性配置文件,内容一般为key=value列表;它被Spring加载到Environment;@PropertySource(“classpath:….properties”)引用属性源

          • 方式1:类中@Autowired注入Environment后使用其方法getProperty(key[,defaultValue])获取value;key不存在时IllegalStateException;相关方法boolean containsProperty();getPropertyAsClass()

          • 方式2:占位符${key};在每一个参数前@Value(“${key}”),或xml配置中value=”${key}”

        • SpringEL表达式:“#{java代码表达式}”:

  • 依赖注入:建立创建好的Bean之间的依赖关系injection

    • setter()设值注入:

      • bean.xml配置方式:即普通new对象后调用setter(),这耦合,用配置文件解耦,反射调用setter()

        • 在管理Bean的标签< bean>中,添加配置< property name="filed名" ref="引用的对象beanId或引用其他的配置Bean,即级联赋值" value="field赋值">列表;value做属性是当类型为基本类型和字符串时的< value>的简写

        • 注入自定义对象类型:外部Bean常见于一对一关系;内部Bean:常见于依赖的对象存在一对多关系

          • < property>内嵌< bean class="依赖的Bean">,或用ref指向引用的Bean,< ref bean="beanId">,减少Bean的解析和创建

          • 当name值是ref指向的对象的属性时,以.级联,如依赖User,则< property name="user" ref="user">;< property name="user.name",value="">;类中需要提供User的getter()

        • 注入集合类型:当依赖类型是数组或集合

          • < property>中嵌套< array>、< set>、< list>或< map>标签,标签中为< value>列表;list包括数组;map内嵌标签或< entry key="" value="">

          • 抽取为公共:引入名称空间xmlns:util及spring-util后,使用< util>标签及< value>或< ref>列表;在注入处ref引用utilId

        • 当value为null,其下加入< null/>替代;等同于默认不注入

          • 当value存在特殊符号,使用CDATA表达式;当特殊符号是尖括号< >,可写为& lt;和& gt;

      • 基于P-名称空间方式的简化配置(property):< beans>中添加xmlns:p属性引入该模式,然后取消< property>,直接在< bean>中添加属性:< bean id class p:bname / bauthor / bname-ref / bauther-ref>;用参数名不灵活,可用参数索引,如p:_0 / _0-ref;只有一个参数时0可省;标识符不以数字开头,加了下划线;无法针对集合参数

      • 简单、直观,与传统的JavaBean的写法更相似,程序开发人员更容易理解、接受,因此常用

      • 问题:配置代码冗余;被注入的对象多次创建(每一个bean标签解析都创建一次Bean),消耗内存

    • 有参构造器注入:

      • bean.xml配置方式:反射时执行有参构造器,利用参数对成员变量进行初始化;配置< constructor-arg name="" ref=”引用的beanId”“ value=”field字面量” index=“参数索引,type="参数类型",三选一但建议不用type,多参数且参数类型相同时无法区分>

      • 简化配置:使用C命名空间(constructor-arg),c-作为bean标签的属性

      • 构造器中参数顺序决定依赖关系的注入顺序;顺序和个数保持一致;参数个数任意(重载);重载时参数个数相同时,标签上需要type明确需要注入参数的类型,否则按顺序注入,结果不正确;(类型相同则不是重载方法了)

      • 没有setter方法,所有的依赖关系全部在构造器内设定,无须担心后续的代码对依赖关系产生破坏;所以对于依赖关系无需变化的Bean,构造注入更有用处

      • 依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系,对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则

      • 构造器过于臃肿,难以阅读

      • Spring在创建Bean实例时,需要同时实例化其依赖的全部实例,因而导致性能下降

  • 循环依赖与三级缓存

    • 发生在Bean属性设置-初始化阶段: 多个Bean之间相互依赖 ,即依赖被递归,形成闭环;如A类拥有属性B类,B类拥有属性C类,C类拥有属性A类

    • 遵循Spring底层,即依赖的注入方式是setter()且依赖是单例的,则不会出现循环依赖问题;假如使用构造器注入,则实例化时,new A(new B(new A...))产生无限嵌套,显然不合理;而使用set时,分别实例化A和B后,依次调用a.setB(b)和b.set(a)毫无问题;在spring容器中还要求类是单例的(纯javaCode此无要求)

    • 三级缓存-DefaultSingletonBeanRegistry:解决循环依赖问题:核心:实例化与初始化分开

      public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
      	// 一级缓存:单例池,即单例Bean的缓存,存放已初始化完成的bean对象
      	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
      	
          // 三级缓存:ObjectFactory的缓存,存放可以生成Bean的工厂;三级缓存的value类型ObjeectFactory是一个函数式接口,它能保证整个容器运行过程中,同名Bean对象唯一
      	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
      	
          // 二级缓存:早期Bean实例的缓存,即暴露了但Bean的生命周期还未结束的早期Bean对象(属性未填充完)
      	private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
          
          // 尝试从容器中获取Bean
          public Object getSingleton(){...}
      }
      
      // 非单例Bean每次从容器获取,都会新建而没有缓存;所以循环依赖后会异常
      /* 流程:
          创建A,需要依赖B且容器中找不到,则暂将A放入三级缓存,先建B;
          建B,需要依赖A,则按一级、二级、三级的顺序依次查找A,在三级中找到A,将A放入二级缓存并删除三级中的A;
          B建完,放入一级缓存,此时B依赖的A还是创建中(实例化但未初始化)状态,即A提前暴露;
          接着创建A,直接从一级缓存中获取B,完成创建,并放入一级缓存
      */

  • Bean注入异常:org.springframework.beans.factory.BeanCreateException:Error creating bean with name "...",NoSuchBeanDefinitionException:可能原因:Bean未添加注解;或注解添加错误,如错选成Dubbo的@Servcie

  • 实现一个IoC容器:

    • 先准备一个基本的容器对象,包含一些map结构的集合,用来存储Bean对象

    • 进行配置文件的读取工作或注解的解析工作,将需要创建的Bean对象封装成BeanDefinition对象存储在容器中

    • 容器将BeanDifinition对象通过反射方式实例化,以完成Bean对象的实例化

    • 为Bean对象的属性赋值即初始化,依赖注入,以完成整个对象的创建

    • 通过容器获取Bean对象,进行业务操作

    • 提供销毁操作,对象不用或容器关闭时,Bean销毁

1.4 AOP

  • 与OOP互为补充,即用来应对许多不容易用传统面向对象编程实现的功能,业务逻辑各部分之间的耦合度,提高程序复用性;比如处理系统中分布于各个模块(不同方法)中的交叉/横切关注点的问题,处理一些具有横切性质的系统级服务,如Authentication权限、Caching缓存、Context passing内容传递、Error handling错误处理,Lazy loading懒加载、Debugging 调试、logging日志,tracing,profiling and monitoring 记录跟踪优化校准、Performance optimization性能优化、Persistence持久化、Resource pooling资源池、 Synchronization同步、Transactions事务处理等,将程序运行过程分解成各个切面,即横切逻辑的编程思想

    • 切面为基本单位的程序开发;OOP以对象为基本单位,POP以过程(方法)为基本单位

    • 如登录功能,查询数据库中密码并判断后跳转页面;添加权限判断功能,原始方法为if(管理员),else(普通用户),这将修改登录功能源码,而AOP将功能抽取出来后切入

  • 底层原理:AOP其实是IoC的一个扩展功能,在IoC的Bean创建的后置处理过程BeanPostProcessor中完成对象的动态代理,它通过jdk或cglib的方式生成代理对象,方法调用DynamicAdvisorInterceptor类的intercept()开始创建,创建过程中创建出advice,pointcut等

  • 基本概念:

    • 横切关注点:散布于应用中的功能

    • 通知、增强处理Advice:将横切关注点抽象和模块化,成为增强方法,是动态代理中的额外功能;增强类是一个Bean即需要@Component或< bean>配置

    • 连接点Joinpoint:执行增强的目标方法,如增删查改方法;方法级别;程序执行过程中明确的点,如方法的调用,或者异常的抛出;在Spring AOP中,连接点总是方法的调用

    • 切入点Pointcut:实际执行增强处理的连接点,如只对删除方法做了增强;通知不能阻止切入点的执行,除非通知中抛出异常

    • 切面Aspect或Advisor:整合通知与切入点的过程

    • 织入:把切面应用到目标对象(的编译期如AspectJ、类加载期、运行期)并创建代理对象

  • AspectJ:独立的框架,定义了基于注解的AOP语法;引入依赖即可使用

    • 编写实现MethodBeforeAdevice的before()等不同切入位置的代理类,创建该Bean;MethodInterceptor接口提供更多方法

      • 根据需求使用传入的参数:分别封装了Method目标方法,Object[]目标方法的参数,Object目标对象

    • 切入点表达式:execution(* 完全限定名.*(..));用作注解的value或标签的expression

      • 第一个 * 表示任意访问权限,..表示方法参数列表; * 可写为具体类(类切入点,影响类中所有方法)、包或具体切入点方法;对包下的子包不起作用

      • 切入点函数:execution最常用,args和within;函数的逻辑运算and和or;xml配置中不能用&&;and不用于同一种函数

    • 注解方式(推荐)及对应xml配置方式:

      • @EnableAspectJAutoProxy(proxyTargetClass):开启AspectJ,生成代理对象,使得增强类可用;配合@Configutation和@ComponentScan实现零配置的完全注解方式《==》< aop:aspectj-autoproxy>

        • @Aspect:标注类为增强类

        • @Order(数字):一个切入点被多个增强时,设置优先级;越小优先级越高;越先执行越后结束

        • 对增强类中的增强标注类型及执行时机:

          • @Before:切入点的前置执行;《==》< aop:before>

          • @Around:此通知方法必有参数ProceedingJoinPoint,在方法体中任意位置通过proceed()方法来调用切入点,即控制转移,执行完后返回继续执行后边代码;《==》< aop:around>

          • @After:切入点后置执行,一定执行;《==》< aop:after>

          • @AfterReturning:切入点正常执行后执行;《==》< aop:after-retuining>

          • @AfterThrowing:切入点异常执行后执行;它若执行,导致AfterReturning和Around的后置不被执行;《==》< aop:after-throwing>

          • 以上为Spring4(SpringBoot1)版本的执行顺序;Spring5(springBoot2)的顺序将@After改为在@AfterReturning或@AfterThrowing之后执行

        • @Pointcut():提取公共切入点,即如果切入点表达式相同,则抽取成一个有空方法体的方法,方法名作为各个增强类型的value值,以实现复用;《==》< aop:pointcut id=”方法名” expression=”execution(…)”>

        • 注解方式不需要标注切入点,被用在切点表达式中的方法自动成为切入点;而xml配置需要:< aop:config>配置增强,其下的:

          • <aop:aspectj-autoproxy />:开启注解支持;属性proxy-target-class为true时指定动态代理为cglib形式

          • < aop:pointcut id="" expression="">列表指明切入点

          • < aop:aspect ref>列表指明切面,其下的< aop:before等 method="" pointcut-ref="pointcutId">

          • < aop:advisor advice-ref="" pointcut-ref="">

        • Spring中如果类被代理,则通过原始bBan的id获取的对象是代理Bean;Proxy动态代理类由Spring通过动态字节码技术在JVM中创建和管理

  • 拦截器Interceptor:AOP运用

    • 拦截器依赖Web框架而不依赖Servlet容器,是Spring容器内的;因此能够深入到方法前后、异常抛出前后,更具有弹性

    • 基于动态代理和反射

    • 可以被多次调用

    • 只能拦截Controller请求,不能拦截静态资源的访问

    • 能访问Spring里的任何资源和对象,如获取IoC容器中的Bean

    • 与过滤器Filter的区别

      • 过滤器依赖Servlet容器,只能用于Web程序,是Servlet容器内的;只在Servlet前后起作用

      • 基于函数回调

      • 只能在容器初始化时调用一次

      • 能拦截所有请求

      • 不能访问域中数据,不能获取Bean

        /**
           * 声明本类为切面@Aspect
           * 声明切点@Pointcut,切在execution指定的所有方法(第一个*)处;切点是连接点的集合;spring中每一个方法称为连接点/最小单位
           * 声明通知advice,@Before,@After...,属性指定切点及指定位置/切入时机,方法体为该切点的具体实现/切入内容;(有点儿类似继承了方法并重写,各通知之间为重载)
           *
           * AOP使用代理模式:
           *  获取bean,断点可见原生类(target,一般就为bean id)和代理类(proxy);查看源码可知代理类是在spring容器初始化时产生
           */
        @Aspect
        @Component
        public class SpringAOP {
        
            @Pointcut("execution(* com.zgh.springbootstudy.service.*.*(..))")
            private void addPointcut() {
            }
        
            @Before("addPointcut()")
            public void beforeAdvice() {
                // 具体的实现
            }
            @AfterReturning("addPointcut()")
            public void afterReturningAdvice() {
                System.out.println("切点=================================");
            }
        }

1.5 数据持久化与事务管理

  • 传统JDBC:太底层,即使用户执行一条最简单的数据查询操作,过程繁琐:获取连接→创建Statement→执行数据操作→获取结果→关闭Statement→关闭结果集→关闭连接,还需要异常处理

  • 方式1:JdbcTemplate:Spring对JDBC的薄层封装,减少样板代码;xml中< bean>定义该类,注入它的dataSource属性;CRUD方法参数为SQL语句和动态赋值语句中?处的可变参数;queryForObject()返回想要的自定义类型数据;batchXXX()批量操作

  • 方式2:Spring整合第三方ORM框架,如Mybatis,Hibernate(JPA)

  • 事务

    • 编程式事务:传统事务管理:建立连接 -》 开启事务 -》异常捕获 -》无异常,则提交;有异常,则回滚;硬编码,重复且复杂

    • 声明式事务:交由Spring基于AOP的事务管理

      • Spring提供事务管理器接口PlatformTransactionManager,不同框架有不同实现类,如JpaTransactionManager,DataSourceTransctionManager,HibernateTransactionManager

      • 配置事务管理器:将具体事务管理器定义为Bean,且注入数据源DataSource

      • 开启事务注解支持:< tx:annotation-driven transaction-manager="事务管理器">;类或方法上添加注解@Transactional;查询方法没必要事务管理

      • 配置事务属性:@Transactional(参数设置)

        • 隔离级别isloation:读已提交READ_COMMITTED,可重复读REPETABLE_READ(MySQL默认),串行SERIALIZABLE;ISOLATION_DEFAULT使用默认数据库隔离属性

        • 传播行为propagation:REQUIRED(默认,增删改),SUPPORTS(查时推荐);一个查询模块调用了另一个模块的方法或接口,因为跨事务而可能报权限(增删改)问题,就需要将该方法的propagation设置为REQUIRED

        • 是否只读readOnly:用于只有查询的业务,提高效率;默认值false

        • 事务提交超时时间timeout:默认-1秒,最终由数据库指定

        • 回滚机制rollbackFor/noRollbackFor:Spring默认对RuntimeException及子类采用回滚策略,对Exception及子类采用提交策略

      • xml方式:配置通知< tx:advice>

      • 零配置/完全注解方式:配置类上@EnableTransactionManagement;类中@Bean定义数据库、JdbcTemplate和事务管理器

      • 可手动指明执行事务操作:getTransaction()开启事务,rollback()回滚,commit()提交;消除JDBC的自动提交:connection.setAutoCommit(false)

      • @TransactionalEventListener:配置事务的回调方法

1.6 参数校验

  • JSR303数据校验:JavaEE6的规范提案的一项子规范:Bean Validate;而Hibernate Validator是参考实现,内置了一些实现如@Length,@Range,@Email;这两套字段校验注解说明:

注解说明
@Null/@NotNull只能/不能为null
@NotEmpty/@NotBlank不为null且不能为空字符串或集合大小大于0/只针对字符串
@AssertTrue/@AssertFalse只能为true/false
@DecimalMax(value)/@DecimalMin(value)不大于/不小于指定数字的数值
@Digits(i,f)只能为小数,整数部分位数最多i位,小数部分最多f位
@Future/@Past只能为将来/过去的日期
@Max(value)/@Min(value)/@Size(max,min)不大于/不小于/指定数字/指定区间的数值
@Pattern(regexp="")满足指定的正则表达式
@Email邮箱地址
@Length字符长长度限制
@Range元素限制
  • Bean层在属性上使用单层或多个注解组合:

  /**
   *  @ConfigurationProperties
   *      用于创建到配置文件中的映射;前提是此类被注册为了一个组件,如@Component
   *      默认加载全局配置文件;如果需要加载自定义文件,使用@PropertySource指定
   *  @Value:
   *      如果特定指定某一个属性,则使用@Value("${key}")或@Value("#{SpEL}")
   *  这等同于XML配置文件写法:
   *   <bean class = "StudentTest">
   *       <property name="id" value="${key}"或value="#{SpEL}"></property>
   *  松散语法绑定:
   *      @ConfigurationProperties,lastName等同于last-name等同于last_name且不区分大小写(无论在配置文件还是类中);不支持SpEL
   *      而@Value严格遵守命名;支持"#{SpEL}"
   *      @ConfigurationProperties可以配合@NotEmpty等校验注解,@Value不支持
   *  复杂类型封装:@ConfigurationProperties支持,@Value不支持(所以适用于项目各处需要零散取值的地方使用)
   */
  // @PropertySource(value = {"classpath:userDefined.properties"})
  @Component
  @ConfigurationProperties(prefix = "test")
  @Validated
  public class User {
      // 取值写法:@Value("${test.id}")
      // 自定义值(表达式)写法:@Value("#{2 * 5}"),@Value("true")
      private Integer id;
      private String name;
      private boolean max;
      private Date date;
      @Email(message = "邮箱地址不符合规范!")
      // @NotEmpty("该字段必填!")
      private String email;
  }
  • 控制层使用@Valid校验,并将校验结果放入BindingResult对象中,校验不通过时可获取到自定义的message错误提示信息

public ResponseResult login(@Valid User user, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        for (ObjectError error: bindingResult.getAllErrors()) {
            return ResponseResult.failure(errror.getDefaultMessage());
        }
    }
}
// 此处可以抽离出来做一个全局异常处理
  • 如果是单个参数校验,则可直接将校验注解写到接口参数中,如:public ResponseResult login(@NotEmpty(message = "密码必要!") String password)

  • 自定义参数校验注解:

  • 注意日期格式化@JsonFormat不属于Validator;将对应数据库时间输出到前端的格式化,在字段上使用注解:@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8");时间在数据库中存储为DATETIME格式,Java中使用Date类,Dao中映射如果是jdbcType=DATE,则时间的时分秒全为0,需要改映射为TIMESTAMP才能显示时分秒

  • 在非控制层的参数校验:由于不能使用BindingResult,需要自定义校验器Validator;在需要校验的地方调用此校验器并传入要校验的对象即可

    public static <T> void beanValidate(T object) {
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); // 初始化校验器
        Set<ConstraintViolation<T>> constraintViolations = validator.validate(object); // 校验
        if (CollectionUtils.isNotEmpty(constraintViolations)) {
            String message = constraintViolations.iterator().next().getMessage();
            throw new ValidationException(message);
        }
    }

  • Assert类:异常处理与参数校验,避免过多使用Throw new ServiceException;常用方法:

    • 逻辑判断:isTrue()、state()

    • 对象或类型断言:notNull()、isNull()、isInstanceOf()、isAssignable()、

    • 文本断言:hasLength()、hasText()、doesNotContain()

    • 集合或数组判断:notEmpty()、noNullElements()

1.7 整合日志

  • log4j

    • 引入依赖;配置log4j.properties并设置日志级别为info,将日志输入到控制台和指定文件

       log4j.rootLogger=DEBUG,console # dubug模式,控制台输出
       log4j.logger.org.mybatis = DEBUG
       log4j.appender.console=org.apache.log4j.ConsoleAppender
       log4j.appender.console.Target=System.out # 标准输出
       log4j.appender.console.layout=org.apache.log4j.PatternLayout
       log4j.appender.console.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n # 日期格式,类名,方法名,行号,换行等

    • 编写增强类LogAspect,通过连接点类ProceedingJoinPoint获取目标类名和方法,调用LOG.info()记录进入方法时日志信息,peoceed()被try-catch,且LOG.error记录异常信息

1.8 数据缓存

  • 启用缓存支持:@EnableCaching标注配置类;对应< cache:annotation-driver>;工作方式:它们会创建aspect并触发缓存pointcut,aspect从缓存中添加和提取数据

  • 如果没有使用缓存,则每次请求或者方法的调用都会查询数据库,输出语句每次都打印;使用缓存,则第一次查询数据库后,后面的相同查询从缓存中获取(以key搜索),而不会调用方法,即输出语句不打印,日志不打印

  • 配置类中配置缓存管理器Bean:ConcurrentMapCacheManager:基于内存(生命周期随应用);SimpleCacheManage:;NoOpCacheManager:;CompositeCacheManager:;EhCacheCacheManager:;RedisCacheManager:SpringDataRedis提供;CompostiteCacheManager构建List,迭代使用多个管理器

  • 缓存设置:不同缓存方式对应不同标签和配置;< cache>< cache name="缓存名" maxBytesLocalHeap="最大堆存储" timeToLiveSeconds="存活时间">

  • 使用缓存:

    • 以上标签被包裹于< chche:advice>:定义缓存通知,配合< aop:advisor advice-ref=”adciveId” pointcut>将通知应用到切点

    • @Caching:缓存注解的组合;< cachechching>

      可用于类上(对所有方法作用)

    • @CacheEvict:在缓存中清除值;常用于remove();< cache:cache-evict>

    • 共有属性:cache;value缓存名;condition是否将缓存应用到方法调用上,SpringEL表达式,不应用则不查找也不缓存;key通过key查找,如通过id查找的方法,key为传递到方法的id参数,SpringEL表达式自定义key,略;method;unless是否将返回值存放缓存(true为不放),之前会从缓存查找

    • @CachePut:不检查缓存,方法始终被调用,值存放到缓存;< cache:cache-put>

    • @Cacheable:标注的方法调用之前,从缓存查找方法返回值;无则方法调用并将值存储到缓存;常用于频繁请求如findOne();< cache-cacheble>

    • 缓存技术:系统性能提升,业务效率提升,分担数据库操作压力

      • 如数据保存到数据库,但高频数据每次查询数据库则低效,耗时,低性能

      • 将热门数据/中间临时数据(如10分钟有效的验证码)保存到缓存,有则从缓存查询获取,无则再查数据库

        • JSR107:java caching(Java缓存技术,难度较大)

          • 5个核心接口:

            1. CachingProvider:创建、配置、获取、管理、控制CacheManager(监控CacheManager的生命周期)

            2. CacheManager:监控多个但名称唯一的Cache的生命周期(类似数据库连接池,每一个Cache是一个连接)

            3. cacheNames/value:Cache名

            4. key:缓存使用的key,默认为方法参数的值;可自定义key生成策略

            5. cacheManager:缓存器

          • condition:符合条件才缓存,如缓存经常登录的用户信息

          • unless:否定缓存,即默认缓存,除非条件成立时才不缓存

          • sync:异步模式

          • Cache(主要,重要):类似map的数据结构,临时存储以key为索引的值

          • Entry:存储在Cache中的key-value对

          • Expiry:缓存有效期

        • Spring缓存抽象:可看做降低难度的JSR107

          • @Cacheable:注解方法,根据方法参数,缓存结果,一般用于service层

          • 缓存查询时,以key搜索是否存在;key存在即缓存中存在,则不需要调用方法(查数据库)了

          • @CacheEvict:清空缓存

          • allEntries:是否清楚此缓存中的所有数据

          • beforeInvocation:是否在方法执行之前执行,默认在方法之后执行清除缓存,所以当方法出现异常,缓存不会被清除

          • @CachePut:保证方法一定被调用,即先调用方法,缓存结果

          • @Cacheable的区别:同步更新

          • 如执行查询-更新-查询的步骤,在更新后的查询,@Cacheable从缓存中的查询没有更新,而@CachePut的缓存中与数据库同步更新了

          • @EnableCaching:开启基于注解的缓存,用于启动类上

          • @Cache:组合缓存规则

        • 第三方:Redis:

          • Redis可用作数据库、缓存、消息的中间件

  • 异步任务

    • 启动类上开启注解@EnableAsync

    • request方法上使用注解@Async

  • 定时任务

    • 开启注解@EnableScheduling

    • 方法使用@Scheduled,指定cron属性值:(core = "")

  • 邮件任务:

1.9 注解驱动编程

  • Spring注解发展:

    • 2.0:提供基础注解,如@Component,@Service,为了简化配置但不能完全替代

    • 3.0:提供更多配置,如@Configuration,@Bean,提倡注解编程

    • 4.0:完全注解编程,产生SpringBoot

  • 配置文件和注解同时存在时,配置覆盖注解,且@Bean优先级高于@Component

  • 注解:

    • @Component:工厂创建对象,等同于< bean>;为了更准确表达类型及作用,衍生注解:

      • @Repository:Dao层(不整合mybatis情形)

      • @Service:Service层

      • @Controller:Controller层

      • @Configuration:配置类

        • @Bean:支持创建复杂对象;属性自定义beanId

    • @Scope:设置bean作用域

    • @Lazy:懒加载即延迟创建单例对象;默认非懒加载

    • @Conditional:判断条件,满足的才注册bean

    • @PreDestroy,@PostConstrut,@Product:JSR250提供,在bean被容器销毁之前的一些扩展工作,在Bean创建完成后的一些扩展工作

    • @Autowired:对setter()注入的封装,基于对象类型;配合@Qualifier可设置基于名字;可位于setter()或属性上;JSR250提供@Resource基于名字,且名字不匹配时再基于类型注入

    • @Value:等同于标签中的value属性,值存储在key-value形式的配置文件中,以${key}获取;不能用于静态属性

    • @PropertySource:读取配置文件件,等同< context:property-placeholder>

    • @Import:常用于导入第三方包中的组件

    • @ComponentScan:bean扫描,等同< context:component-scan>

      • 包含策略:< context:include-filter type="" expression="" use-default-filters="false">

      • 排除策略:< comtext:exclude-filter type="" expression="">

      • type可取值及expression指定值:

        • assignable:特定类型

        • annotation:特定注解

        • aspectj:切点表达式,expression指定包或类

        • regex:正则表达式

        • custom:自定义

1.10 代码

  • 自定义单元测试:

// 使用Junit来做单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootstudyApplicationTests {
    @Autowired
    private DataSource dataSource;
​
    @Test
    public void testConnectDataSource() throws SQLException {
        // 连接数据库测试:打印出
        // class com.zaxxer.hikari.HikariDataSource;使用debug可以查看配置文件中的属性是否有效
        // HikariProxyConnection@715602332 wrapping com.mysql.jdbc.JDBC4Connection@11eed657
        // 即使用自带Tomcat连接池的HikariDataSource数据源;使用JDBC4Connection来连接数据源
        // 可自定义连接池如c3p0,druid,数据源;源码在jdbc包下
        System.out.println(dataSource.getClass());
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }
​
    // 打印出配置类中获取的值
    // 测试使用配置类来注册bean组件:应用上下文中是否包含了某一个bean组件
    @Autowired
    private StudentTest studentTest;
    /**
     * spring容器是管理对象的地方:创建-装配-销毁;此控制权交由spring,因此成为IOC容器
     * 核心接口:BeanFactory(生产),ApplicationContext(应用上下文,管理)
     */
    @Autowired
    private ApplicationContext ioc;
​
    @Test
    public void contextLoads() throws SQLException {
        System.out.println(studentTest);
    }
​
    @Test
    public void testUserService() {
        System.out.println("添加组件:userService");
        boolean exist = ioc.containsBean("userService");
        System.out.println(exist);
    }
​
    // 日志测试
    @Test
    public void testLog() {
        new LogUtils().testLog();
    }
}
  • 一些注解的使用:

    /**
     * 组合注解
     * @Target({ElementType.TYPE})
     * @Retention(RetentionPolicy.RUNTIME)
     * @Documented
     * @Inherited
     * @SpringBootConfiguration:标注配置类,使用类而不是配置XML文件的方式
     * @EnableAutoConfiguration:标注可自动导入组件:@AutoConfigurationPackage,@Import
     *      配置文件读取的原理,组件bean的装配原理,主要就在这几个注解的源码中;其实配置文件中的属性就是封装的类属性,有什么属性说明源码中都有对应属性的定义
     * @ComponentScan(
     *     excludeFilters = {@Filter(
     *     type = FilterType.CUSTOM,
     *     classes = {TypeExcludeFilter.class}
     * ), @Filter(
     *     type = FilterType.CUSTOM,
     *     classes = {AutoConfigurationExcludeFilter.class}
     * )}
     * )
     */
    // SpringBoot不自动识别XML组件配置文件,如果像spring一样自己编写配置文件,则需要@ImportResource导入;
    // 但Springboot推荐配置类,即使用注解:@@Configuration,@Bean
    // @ImportResource(locations = {"classpath:userDefined.xml"})
    // 如果只需要简单的启动运行,则可加exclude值,否则需要在配置文件中配置DataSource
    @SpringBootApplication
    @EnableCaching
    public class SpringbootstudyApplication {​
        public static void main(String[] args) {
            SpringApplication.run(SpringbootstudyApplication.class, args);
        }
    }

  • @Configuration:

    /**
     * 向容器添加映射组件@Configuration,自定义映射Druid数据源,绑定和映射配置文件中黄色背景的属性
     * Druid的优点在于监控,监控发送到数据库的SQL语句,方便排错
     */
    @Configuration
    public class DruidDataSourceConfig {
        @ConfigurationProperties(prefix = "spring.datasource")
        @Bean
        public DataSource useDruid() {
            return new DruidDataSource();
        }
    ​
        // 配置web监控、过滤器;高版本Druid可在yml中配置:url-pattern,web-stat-filter,exclusions
    }
    @Configuration
    public class MybatisConfig {
        // 用于设置实体bean的属性命名与数据库列名映射
        @Bean
        public ConfigurationCustomizer configurationCustomizer() {
            return new ConfigurationCustomizer() {
                @Override
                public void customize(org.apache.ibatis.session.Configuration configuration) 
                    configuration.setMapUnderscoreToCamelCase(true);
                }
            };
        }
    }
    // 指明当前类是一个配置类(显式配置),替代spring的配置文件XML(<bean id="" class=""></bean>)(显式配置)
    // 装配机制还有一种隐式配置(推荐):自动发现(组件扫描)和装配,
    @Configuration
    public class UserConfig {
        // 将方法返回值添加到容器,容器中此组件id默认为方法名;
        // 这种方式常用于装配第三方bean;即此处直接在UserService类上使用@Service即可
        @Bean
        public UserService userService() {
            return new UserServiceImpl();
        }
    }

  • 权限校验:

    /**
     * 安全,权限校验,验证
     * 1、控制请求的访问权限
     */
    // 需要编写配置类或配置文件;此注解已包含@Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // super.configure(http);
        }
    }

  • 扩展SpringMVC:

    /**
     * 扩展springMVC:可以理解为springboot独特的controller层
     * 因此:springboot即保留了springMVC,也能使用自己的配置类
     * 使用@EnableWebMvc全面接管springMVC,即对springMVC的自动配置controller不再有效(核心@Import失效),包括静态资源都访问不了
     */
    // @EnableWebMvc
    @Configuration
    public class WebMVCConfig implements WebMvcConfigurer {
        // ctrl+o打开本类拥有的所有方法(包括继承的),如此快速导入需要重写的父类方法
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            // 参数1为请求的URL,参数2位返回值,即资源文件HTML名;
            // 这等同于controller中自定义的@RequestMapping等标注的方法;页面同理可访问;是为扩展
            // 因此,一条语句便可代替controller中一个方法;这通常用于返回首页等方法体不复杂的controller替换
            registry.addViewController("success").setViewName("ymldefinetest");
            registry.addViewController("/").setViewName("index");
            // registry.addViewController("/login.html").setViewName("login");
        }
    ​
        @Bean
        public LocaleResolver localeResolver() {
            return new I18NResolverUtil();
        }
    }
    @RestController
    public class EmployeeController {
        @Autowired
        private EmployeeMapper employeeMapper;
    ​
        /**
         * get请求的两种参数设置方式:
         *  1、将参数作为路径的一部分,如下实现;
         *  2、将参数拼接在问号?后面,多个参数用&符号;使用的注解@RequestParam
         */
        @GetMapping("/employee/{id}")
        public Employee getEmployee(@PathVariable("id") Integer id) {
            return employeeMapper.queryEmployeeById(id);
        }
    ​
        @GetMapping("/employees")
        public List<Employee> getEmployees() {
            return employeeMapper.queryEmployees();
        }
    ​
        // 使用了get方式,根据SQL语句,在页面上输入/employee?name=xxx来完成请求发送
        @GetMapping("/employee")
        public Employee addEmployee(Employee employee) {
            employeeMapper.addEmployee(employee);
            return employee;
        }
    }

  • 原生JDBC连接和操作数据库:

/**
 * 使用原生JDBC连接和操作数据库
 */
@Controller
public class JDBConnectController {
   @Autowired
    private JdbcTemplate jdbcTemplate;

    @ResponseBody
    @GetMapping("/queryUser")
    public Map<String, Object> queryUser() {
        String sql = "select * from user";
        List<Map<String, Object>> users = jdbcTemplate.queryForList(sql);
        return users.get(0);
    }
}
  • AOP:

/**
 * 声明本类为切面@Aspect
 * 声明切点@Pointcut,切在execution指定的所有方法(第一个*)处;切点是连接点的集合;spring中每一个方法称为连接点/最小单位
 * 声明通知advice,@Before,@After...,属性指定切点及指定位置/切入时机,方法体为该切点的具体实现/切入内容;(有点儿类似继承了方法并重写,各通知之间为重载)
 *
 * AOP使用代理模式:
 *  获取bean,断点可见原生类(target,一般就为bean id)和代理类(proxy);查看源码可知代理类是在spring容器初始化时产生
 */
@Aspect
@Component
public class SpringAOP {

    @Pointcut("execution(* com.zgh.springbootstudy.service.*.*(..))")
    private void addPointcut() {
    }

    @Before("addPointcut()")
    public void beforeAdvice() {
        // 具体的实现
    }
    @AfterReturning("addPointcut()")
    public void afterReturningAdvice() {
        System.out.println("切点=================================");
    }
}
  • Log4J,slg4j日志:

/**
 * log4j为先出现的日志记录工具(实现层),后来出现slg4j(抽象层)
 * 简单使用一些接口,则slf4j即可;一些复杂的实现,则同时导入各自API设置中间的适配器
 * spring底层日志使用commons-logging,hibernate使用
 * springboot使用slf4j+logback(实现层);
 * 统一日志使用:
 *  spring、hibernate框架移除(不导入)自己的日志,如springboot的maven中引入spring-core的时候就有<exclusion>把commons-logging移除了
 *  使用中间包替换(适配器模式;否则启动失败,因为没有导入),转为slf4j
 *      logback-classic.jar,jul-to-slf4j.jar,log4j-over-slf4j.jar;它们的源码中都是实例化的slf4j生产工厂对象或者调用其接口
 *  使用slf4j及其实现
 */
public class LogUtils {
    final Logger LOGGER = LoggerFactory.getLogger(getClass());

    /**
     * 日志级别依次升高;springboot默认info,即只会输出info及以后的;
     * 配置文件配置:
     *  logging.level:指定文件的日志级别
     *  logging.path:指定日志生成的路径,springboot默认日志文件名spring.log
     *  logging.file:指定日志生成文件,包含了路径,当前相对路径为项目下
     *  logging.console:指定日志输出格式
     */
   
    public void testLog() {
        LOGGER.trace("===trace: ");
        LOGGER.debug("===debug: ");
        LOGGER.info("===info: ");
        LOGGER.warn("===warn: ");
        LOGGER.error("===error: ");
    }
}
  • 针对实体Bean的一些注解:

/**
 *  @ConfigurationProperties
 *      用于创建到配置文件中的映射;前提是此类被注册为了一个组件,如@Component
 *      默认加载全局配置文件;如果需要加载自定义文件,使用@@PropertySource指定
 *  @Value:
 *      如果特定指定某一个属性,则使用@Value("${key}")或@Value("#{SpEL}")
 *  这等同于XML配置文件写法:
 *   <bean class = "StudentTest">
 *       <property name="id" value="${key}"或value="#{SpEL}"></property>
 *  松散语法绑定:
 *      @ConfigurationProperties,lastName等同于last-name等同于last_name且不区分大小写(无论在配置文件还是类中);不支持SpEL
 *      而@Value严格遵守命名;支持"#{SpEL}"
 *  JSR303数据校验(JavaEE6的规范提案的一项子规范:Bean Validate;Hibernate Validator是参考实现,内置了一些实现如@Null,@Max,@Email):
 *      @ConfigurationProperties可以配合@NotEmpty等校验注解,@Value不支持
 *  复杂类型封装:@ConfigurationProperties支持,@Value不支持(所以适用于项目各处需要零散取值的地方使用)
 */
// @PropertySource(value = {"classpath:userDefined.properties"})
@Component
@ConfigurationProperties(prefix = "test")
@Validated
public class StudentTest {
    // 取值写法:@Value("${test.id}")
    // 自定义值(表达式)写法:@Value("#{2 * 5}"),@Value("true")
    private Integer id;
    private String name;
    private boolean max;
    private Date date;
    @Email
    // @NotEmpty
    private String email;

    // ...
}
  • 消息队列:

/**
 * 消息服务/队列:通过消息服务中间件来提升系统异步通信、扩展解耦能力、削峰
 *  原来为程序A向其他N个程序提供服务如提供数据;使用中间件后,即中间插入一个MQ,A只负责把数据发送到MQ,需要数据的自行去MQ中获取消费
 *  A不再关心向谁提供数据,不关心目标是否连接成功,调用成功,失败超时等问题;适用于系统间相互依赖和频繁调用的场景
 *
 *  消息代理:消息发送者发送的消息被消息代理接管
 *  目的地:消息代理保证消息传递到指定的目的地;消息队列的两种目的地:
 *      队列:点对点通信
 *          消息有唯一发送(发布)者;消息在队列中,可以拥有多个接收(消费)者,但一旦被某一个接收者获取,则消息从队列移除即消息有唯一接受(消费)者
 *          如商品秒杀:将用户秒杀成功的信息保存到队列,直到队满;然后慢慢处理队列中的消息,因为每个消息对应唯一消费者
 *      主题:发布和订阅通信
 *          消息被发布成为主题,任意消费者可以订阅(监听)此主题,然后消费者都会消费此消息
 *          如短信、邮件发送,消费者都订阅了短信主题,邮件主题,消费者都能获取短信,邮件消息
 *
 * JMS:Java消息服务(j2ee消息规范):基于JVM消息代理的规范(API,即Java语言,Java平台);
 *  支持的消息类型:text,map,bytes,stream,object
 *  ActiveMQ实现:
 *  HornetMQ实现:
 *
 * AMQP:高级消息队列协议(协议,即跨语言,跨平台),兼容JMS;更细分了两种目的地为5种
 *  支持的消息类型:将消息序列化后以byte[]发送
 *  RabbitMQ实现:
 *      Message:消息头+消息体,消息头可选属性routing-key(路由键),priority(优先权),delivery-mode(是否持久化)
 *      Publisher/Producer:消息生产者,向交换器发布消息的客户端程序
 *      Exchange:交换器,接收消息并根据Binding把消息路由给服务器中的队列;4中类型(转发策略不同):
 *          direct(默认):点对点,routing-key与Binding-key严格相同
 *          fanout(最快):不考虑routing-key,散发消息到与该交换器绑定的所有队列
 *          topic(模糊匹配):模糊匹配routing-key与Binding-key
 *          headers():
 *      Queue:消息队列,保存消息直到消息被消费;一个消息可投入多个队列
 *      Binding:绑定,消息队列与交换器的关联关系和路由规则,可多对多
 *      Connection:网络连接,服务器与消费者之间的通信连接,如TCP连接
 *      Channel:信道,网络连接中划分多条通信链路,为节省资源,复用链路
 *      Consumer:消费者
 *      Virtual Host:虚拟主机,服务器中相对独立的服务器域,包含一批交换器,消息队列和相关对象;服务器相当于多个虚拟主机组成
 *      Broker:服务器
 *
 *  使用:spring引入相关依赖包
 */

2 Spring + JDBC

  • 数据访问对象DAO实现/委托Repository接口;spring提供大量继承自DataAccessException的数据库操作异常体系;spring根据数据的固定和可变,将类分为模板和回调;模板类如JdbcTemplate处理数据访问的固定部分(事务管理,资源管理和异常处理);回调实现数据访问(语句,参数绑定,处理结果集)

  • 配置数据源:

    • NDI数据源:

      • 此种配置让数据源在应用程序之外进行处理,仅在访问时查找即可;JndiObjectFactoryBean实现查找dataSource;对应jee命名空间<jee:jndi-lookup id=”dataSource” jndi-name=”源名” resource-ref=”true”>检索任何JNDI对象及数据源

    • JDBC数据源(每次请求都会新建连接,费性能):spring自带的DriverManagerDataSource/SimpleDriverDataSource/SingleConnectionDataSource(单线程)

    • 连接池数据源(推荐):DBCP/c3p0/JDBC的<bean id class="….dbcp.BasicDataSource”">;<driverClassName,url,username,password,initalSize,maxActive…>对应实现java类及setter()

    • 嵌入式数据库

    • @Profile()运行时选择数据源;属性值”development”使用开发数据源;”qa”QA数据源;”production”生产环境数据源;对应< bean profile="">

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值