Spring5知识整理(二)

  • Spring的表达式语言spEL

  1. Spring表达式语言(SpEL):是一个支持运行时查询和操作对象图的强大表示是语言,是一种可以与一个基于spring的应用程序中的运行时对象交互的东西。总得来说SpEL表达式是一种简化开发的表达式,通过使用表达式来简化开发,减少一些逻辑、配置的编写。
  2. 语法类似于 EL:SpEL 使用 #{...} 作为定界符 , 所有在大括号中的字符都将被认为是 SpEL , SpEL 为 bean 的属性进行动态赋值提供了便利。
  3. 通过 SpEL 可以实现:
    • 通过 bean 的 id 对 bean 进行引用,用了SpEL在bean标签中可以用value代替ref。
    • 可以像EL一样用点运算符调用方法以及对象中的属性。
    • 计算表达式的值
    • 正则表达式的匹配。
  4. SpEL 字面量,意义不大,spring内部本身有数据类型的自动转换机制,直接写值就好了,不必用SqEL,了解:
    • 整数:#{8}
    • 小数:#{8.8}
    • 科学计数法:#{1e4}
    • String:可以使用单引号或者双引号作为字符串的定界符号。
    • Boolean:#{true}

  5. SpEL引用bean , 调用它属性和方法:
    • 引用其他对象:#{car}
    • 引用其他对象的属性:#{car.price}
    • 调用其它方法 , 还可以链式操作:#{person.pet.toString()}
    • 调用静态方法静态属性:#{T(java.lang.Math).PI}
    • Spring EL 操作List、Map集合取值











  6. SpEL支持的运算符号:
    • 算术运算符:+,-,*,/,%,^(加号还可以用作字符串连接)
    • 比较运算符:< , > , == , >= , <= , lt , gt , eg , le , ge
    • 逻辑运算符:and , or , not , |
    • if-else 运算符(类似三目运算符):?:(temary), ?:(Elvis)
    • 正则表达式:#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}'}



  • Spring中的bean的生命周期

  1. 生命周期图解:


  2. spring生命周期:

    1、实例化一个Bean--也就是我们常说的new;

    2、按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;

    3、如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值

    4、如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以);

    5、如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);

    6、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;

    7、如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。

    8、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法、;

        注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述。

    9、当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法;

    10、最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

  3. BeanPostProcessor接口演示:对BeanPostProcessor接口,做一个实现类,演示一下这些接口如果要在项目中自定义的话应该怎么用,生命周期的前后置处理方法的执行情况


  • Spring通过工厂方法进行配置

  1. 在Spring的世界中, 我们通常会利用 xml配置文件 或者 annotation注解方式来配置bean实例!
    在第一种利用 xml配置文件 方式中, 还包括如下三小类
    1. 反射模式(我们前面的所有配置都是这种模式)
    2. 工厂方法模式
    3. Factory Bean模式
    其中反射模式最常见, 我们需要在bean 配置中配置我们需要的bean object的全类名。

     
    上面bean 里面的class属性就是全类名, Spring利用java反射机制创建这个bean object。

  2. 工厂方法模式
    在工厂方法模式中, Spring不会直接利用反射机制创建bean对象, 而是会利用反射机制先找到Factory类,然后利用Factory再去生成bean对象。
    而Factory Mothod的具体使用方式也分两种, 分别是静态工厂方法 和 实例工厂方法。
  3. 静态工厂方法方式所谓静态工厂方式就是指Factory类不本身不需要实例化, 这个Factory类中提供了1个静态方法来生成bean对象



    里面定义了1个静态的bean 容器map. 然后提供1个静态方法根据Car 的id 来获取容器里的car对象。

    xml配置:



    小结
    由上面的例子, 静态工厂方法方式是非常适用于作为1个bean容器, 只不过bean集合定义在工厂类里面而不是项目xml配置文件里面。
    缺点也比较明显, 把数据写在class里面而不是配置文件中违反了我们程序猿的常识和spring的初衷。当然优点就是令人恶心的xml配置文件更加简洁。所以,工厂方法的配置,了解一下就行了,个人建议不要在项目中使用。
     
  4. 实例工厂方法方式
    所谓实例工厂方式也很容易看懂, 就是工厂类里面的getBean 方法不是静态的, 也就是说要先实例1个工厂的对象, 才能依靠这个工厂对象去调用getBean 方法获得bean 对象。





    小结:显然,实例化工厂方法比静态工厂方法,要灵活一些,没把数据写死在工厂类里,但是实际开发中,用的最多的还是反射模式!
  • 用注解的方式来配置Bean

  1. 在实际项目开发中,可能使用注解配置Bean,使用的还要广泛一些,因为更方便简洁!
  2. 什么是注解:
    传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事务,这么做有两个缺点:
    1、如果所有的内容都配置在.xml文件中,那么.xml文件将会十分庞大;如果按需求分开.xml文件,那么.xml文件又会非常多。总之这将导致配置文件的可读性与可维护性变得很低
    2、在开发中在.java文件和.xml文件之间不断切换,是一件麻烦的事,同时这种思维上的不连贯也会降低开发的效率
    为了解决这两个问题,Spring引入了注解,通过"@XXX"的方式,让注解与Java Bean紧密结合,既大大减少了配置文件的体积,又增加了Java Bean的可读性与内聚性。
  3. 注解方式配置:
    Spring注解配置初始化对象(<bean>):
    spring中使用注解配置对象前,要在配置文件中配置context:component-scan 标签告诉spring框架,配置了注解的类的位置
    配置文件applicationContext.xml:
    <context:component-scan base-package="cn.example.bean">
    </context:component-scan>
     
    注解说明:
    Component最初spring框架设计的,后来为了标识不同代码层,衍生出Controller,Service,Repository三个注解 作用相当于配置文件的bean标签,被注解的类,spring始化时,就会创建该对象
    @Component("user") 给类注解
    @Service("user") // service层
    @Controller("user") // web业务层
    @Repository("user")//dao层
    @Scope(scopeName="singleton") 等同于配置文件的scope属性
     
    @Value(value="188") //给值属性赋值,可以用在方法上或者属性上
    @Resource(name="car") //给对象赋值,该值car必须要已经声明(在配置文件中已经配置,或者在类对应中已经注解)
    @PostConstruct //指定该方法在对象被创建后马上调用 相当于配置文件中的init-method属性
    @PreDestroy //指定该方法在对象销毁之前调用 相当于配置文件中的destory-method属性
    @Autowired //自动装配对象赋值@Qualifier("car2") 一起使用 告诉spring容器自动装配哪个对象
  4. 示例:不使用注解




  5. 使用注解:




    也可以:






    @Resource的装配顺序:
    1、@Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配
    2、指定了name或者type则根据指定的类型去匹配bean
    3、指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错
  • @Autowired

  1. @Autowired顾名思义,就是自动装配,其作用是为了消除代码Java代码里面的getter/setter与bean属性中的property。当然,getter看个人需求,如果私有属性需要对外提供的话,应当予以保留。
    因此,引入@Autowired注解,不要忘记配置文件要写

    然后才是在JavaBean的属性上加注解:


    这里@Autowired注解的意思就是,当Spring发现@Autowired注解时,将自动在代码上下文中找到和其匹配(默认是类型匹配)的Bean,并自动注入到相应的地方去。
    有一个细节性的问题是,假设此时我把.xml文件的
    <bean id="monkey" class="cn.ybzy.springtest.Monkey" p:monkeyName="mm"></bean>
    <bean id="tiger" class="cn.ybzy.springtest.Tiger" p:tigerName="tt"></bean>
    行两行给去掉,再运行,会抛出异常,因为,@Autowired注解要去寻找的是一个Bean,Tiger和 Monkey的Bean定义都给去掉了,Spring容器找不到了自然抛出异常。那么,如果属性找不到对应的对象我不想让Spring容器抛 出异常,而就是显示null,可以吗?可以的,就是将@Autowired注解的required属性设置为false 即可:
  2. @Autowired接口注入
    上面的比较简单,我们只是简单注入一个Java类,那么如果有一个接口,有多个实现,Bean里引用的是接口名,又该怎么做呢?比如有一个Car接口:







    这样做的话就会报错,
    Car接口有两个实现类,Spring并不知道应当引用哪个实现类。这种情况通常有两个解决办法:
    1、删除其中一个实现类,Spring会自动去base-package下寻找Car接口的实现类,发现Car接口只有一个实现类,便会直接引用这个实现类
    2、实现类就是有多个该怎么办?此时可以使用@Qualifier注解,指明你要spring装载那个对象:
  3. @inject:
    功能和@Autowired差不多的一个注解@inject,它是jsr330规范的注解,用它的话要导入相应的jar包,推荐使用@Autowired
    <dependency>
      <groupId>javax.inject</groupId>
           <artifactId>javax.inject</artifactId>
           <version>1</version>
    </dependency>
  • component-scan标签详解

  1. base-package: 指定spring扫描注解的类所在的包。当需要扫描多个包的时候,可以使用逗号分隔。如果只希望扫描特定的类,不是扫描包里的所有类的时候,可以使用resource-pattern属性来指定只扫描的包。这标签是需要context的命名空间的。





    只是这样配置,上面test可以访问到所有的有注解的对象!加上resource-pattern来指定只扫描的包:

    这样配置,除了User的对象,其他都找不到了!

  2. 子标签<context:exclude-filter type="annotation" expression=""/>配置在不扫描的类,可以有很多个这样的子标签。

    这样配置,@controller注解的类的对象就找不到了!

  3. 子标签<context:include-filter type="annotation" expression=""/>配置要扫描的类,也可以有多个。

    除了包含的注解以外的注解的类的对象都找不到了!
  4. 上面都是用的type=annotation,下面再看一下assignable

    排除UserDao这个接口以及这个接口的实现类!
  • 泛型的依赖注入

  1. 泛型依赖注入就是允许我们在使用spring进行依赖注入的同时,利用泛型的优点对代码进行精简,将可重复使用的代码全部放到一个类之中,方便以后的维护和修改。同时在不增加代码的情况下增加代码的复用性。








  • Spring的切面编程概述

  1. AOP:AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,这种散布在各处的与具体业务无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
  2. 用log4j这个日志框架来看一看这种“大量代码的重复”的情况:
    1、log4j日志配置文件log4j.properties:
    ### set log levels ###
    log4j.rootLogger = debug , stdout , D
    ### 输出到控制台 ###
    log4j.appender.stdout = org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target = System.out
    log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern = %n %d %p [%l] %m %n
    ### 输出到日志文件 ###
    log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.D.File = ./log.log
    log4j.appender.D.Append = true
    ## 只输出DEBUG级别以上的日志!!!
    log4j.appender.D.Threshold = DEBUG
    log4j.appender.D.layout = org.apache.log4j.PatternLayout
    log4j.appender.D.layout.ConversionPattern = %n %d %p [%l] %m %



    AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
    使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
    Spring的AOP就可以实现核心关注点和横切关注点的分离
    Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
    1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
    2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB(CGLIB是一个强大的、高性能的代码生成库)
    AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
    1、定义普通业务组件
    2、定义切入点,一个切入点可能横切多个业务组件
    3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
    所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
  • 基于注解的Spring的AOP代码实现试验

  1. 基于AspectJ框架来实现Spring的AOP的:
    步骤1: 添加jar包
    <dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.0.6.RELEASE</version>
    </dependency>
     
    步骤2:写Spring的配置文件,把注解扫面打开,写接口和实现类,有main方法是测试类








    步骤3:定义切面类,LoggerAspect


    步骤4:spring的配置文件中,让@Before注解起作用


    可以看到在指定的切入点,切入了切面类的方法,将分离的日志代码和业务代码在运行时合起来了!

    步骤5:继续完善切面类


    步骤6:一个方法实现了,扩展到多个方法,用*代替add方法名,表示任意方法:


    最后,对@Before注解在解释一下:
     
    @Before和它注解的这个方法在AOP里叫:通知(advice),这个我们除了@Before这个叫前置通知(在切入目标方法执行之前执行)外,还有:
    @After后置通知(在目标方法执行之后执行)
    @AfterReturning返回通知(在目标方法返回结果之后执行)
    @AfterThrowing异常通知(在目标方法跑出异常之后执行)
    @Around环绕通知(围绕着方法执行)
     
    @Before注解后的括号的内容叫AspectJ表达式,这里还可进一步使用通配符*

    public int 换成 * : 任何修饰符和返回值类型


    AopTestImpl换成*,表示cn.ybzy.springdemo包里的所有类


    还可用两个点儿表示任意参数


    很多个方法都是相同的切入点表达式,可以像提公因式样的提出来:
  • AOP实现后置、返回、异常和环绕通知

  1. 后置通知: 在切入点的目标方法执行后(无论有异常抛出没的),都会执行这个通知方法!


    如果想要在通知方法里访问到目标方法返回的结果,可以用返回通知,


  2. 返回通知:是在目标方法执行之后没有异常,并且返回结果后才执行通知方法:
  3. 异常通知:当目标对象抛出异常的时候执行通知方法,添加一个div除法方法




  4. 环绕通知:就是把前面4中通知全给整合在一起,环绕目标方法的所有通知的意味。
    使用它必须要求:
    1、必须要带参数ProceedingJoinPoint类型的参数,这个参数可以直接调用原来的目标方法。
    2、环绕通知方法必须有返回值,这个反正值就是目标方法的返回值。


    当同一个目标方法有多个切面的时候,哪个切面先执行,取决于在切面类上的注解@order(值小的先执行)
  • 基于xml配置文件的AOP使用

  1. 前面的例子中,配置切面类,AopTest类还是用注解,我们主要看有切面的通知方法在xml里该怎么配,方法上的注解删除干净。


猜你喜欢

转载自www.cnblogs.com/PCBullprogrammer/p/10130334.html