Spring学习笔记

200 阅读6分钟

一、Spring实战-第一部分.Spring核心知识

  • 第1章将会概要地介绍Spring,包括DI和AOP的一些基本样例。同时,读者还会了解到更大的Spring生态系统的整体情况。
  • 第2章更为详细地介绍DI,展现应用程序中的各个组件(bean)如何装配在一起。这包括基于XML装配、基于Java装配以及自动装配。
  • 在掌握了基本的bean装配后,第3章会介绍几种高级装配技术,读者可能并不会经常用到这些技术,但是如果用到的话,本章的内容将会 告诉读者如何发挥Spring容器最强大的威力。
  • 第4章介绍如何使用Spring的AOP来为对象解耦那些对其提供服务的横切性关注点。这一章也为后面各章提供基础,在后面读者将会使用 AOP来提供声明式服务,如事务、安全和缓存。

(一)装配Bean

1.组件扫描和自动装配

  • @Component :value @Named
  • @Autowired,@Inject
  • @ComponentScan: basePackages,basePackageClasses

2.装配三方库组件--通过Java代码装配Bean

尽管在很多场景下通过组件扫描和自动装配实现Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,因此需要明确配 置Spring。比如说,你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添 加@Component和@Autowired注解的,因此就不能使用自动化装配的方案了。

  • @Configuration
  • @Bean

3.通过通XML装配装bean

  • 声明bean <bean></bean>

  • 注入bean

    • <constructor-arg></constructor-arg>
      • ref属性、value属性

      • <null/><list></list><set></set><value></value><ref></ref>

image.png

image.png

image.png

image.png


      `<property></property>`
       name属性
      `<null/>` ;`<list></list>`、`<set></set>`;`<value></value>`、`<ref></ref>`;

image.png

4.混合配置

  • JavaConfig+JavaConfig
    用@Import注解,将CDPlayerConfig.java、CDConfig.java配置类与SoundSystemConfig.java配置组合在一起。 CDPlayerConfig.java、CDConfig.java也是被@Configuration注解的配置类。
    image.png

  • JavaConfig中引入xml配置
    使用@ImportResource
    image.png

  • xml配置引入JavaConfig
    使用Bean标签引入JavaConfig
    image.png

  • xml配置引入xml配置
    使用<import/>标签
    image.png

(二)、高级装配

1、条件化的bean

实现Condition的matches方法:


public class  MagicBeanCondition implements Condition{
    /**
     *
     * @param conditionContext 可以获得Environment实例
     * @param annotatedTypeMetadata
     * @return
     */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        return environment.getProperty("magic",Boolean.class);
    }
}

使用@Conditional注解

@Component
@Conditional(MagicBeanCondition.class)
public class MagicBean{
    ...
}

如果是显式地定义bean,则可以这样使用:


@Bean
@Conditional(MagicBeanCondition.class)
public MagicBean magicBean(){
    return new MagicBean();
}

这样当MagicBeanCondition.matches方法返回true时,就可以创建magicBean了,反之就不会创建实例。

2、条件化的bean的特例-profile

profile是一种特殊的条件化bean。解决了跨环境部署时,中间件配置或其他配置存在环境差异的问题。最典型的例子就是数据库——各个环境都会配置属于自己的数据库。

(1) 使用@Profile

使用@Profile注解指定某个bean属于哪一个profile,@Profile注解应用在了类级别上。它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。如果dev profile没有激活的话,那么带有@Bean注解的方法都会被忽略掉。

@Configuration
@Profile("dev") // dev profile被激活时才会创建实例
public class DbConfig{
      
      @Bean
      public DataSource db(){

          return new ...;
      }

}

(2) 激活profile

  • 作为DispatcherServlet的初始化参数;

  • 作为Web应用的上下文参数;

  • 作为JNDI条目;

  • 作为环境变量;

  • 作为JVM的系统属性;

  • 在集成测试类上,使用@ActiveProfiles注解设置。

最佳实践
spring.profiles.active和spring.profiles.default组合使用:
a)spring.profiles.default=dev
DispatcherServlet、Web应用的上下文参数配置spring.profiles.default=dev

因此,所有的开发人员都能从版本控制软件中获得应用程序源码,并使用开发环境的设置(如数据库)运行代码,而不需要任何额外的配置。

b)spring.profiles.active按需配置
使用使用系统属性、环境变量或JNDI设置配置spring.profiles.active,又可实现不同环境的按需配置。

3、自动装配的歧义性

(1)什么是自动装配的歧义性

Spring自动装配时,当注入的类型是一个接口,并且程序中存在该接口的多个实现,这多个实现都生成了实例,自动装配就会出现歧义:两个实现我选择哪个呢?

举个例子更具体地说明这个问题:

  • Dessert是一个接口,有若干实现,很显然@Component标注后,Spring会为他们创建实例,它们都是Dessert类型。
@Component
public class IceCream implements DessertDessert
类型{...}
@Component
public class Cake implements  Dessert{...}

  • 现在有个实例依赖Dessert:
@Autowired
public void setDessert(Dessert dessert){
    this.dessert=dessert;
}

这个时候Spring自动装配的时候,就懵逼了:该选哪一个呢?

(2)怎么解决歧义

(a)指定首选项

方式1,@Component+@Primary


@Component
@Primary
public  class IceCream implements Dessert{
    ...
}

如果是显式地声明bean的话,也可以@Bean+@Primary

@Bean
@Primary
public Dessert dessert(){

    return new IceCream();

}
(b)限定自动装配的bean

step1. @Qualifier限定bean

@Component
@Qualifier("cold")
public  class IceCream implements Dessert{
    ...
}

如果是显式地声明bean的话,也可以@Bean+@Qualifier

@Bean
@Qualifier("cold")
public Dessert dessert(){

    return new IceCream();

}

step2. 赖注入时使用@Qualifier指定限定的bean

@Autowired
public void setDessert(Dessert dessert){
    this.dessert=dessert;
}

4、bean的作用域

(1)四种作用域

  • 单例:默认作用域。
  • 原型:每次注入或者通过Spring上下文获取的时候,都会创建一个新的实例。
  • 会话:一个会话对应一个实例。
  • 请求:一个request对应一个实例。

如何修改默认作用域?

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public NotePad notepad {
    return new NotePad();
}

(2)会话和请求作用域装配后如何工作?

问题思考: ShoppingCart作用域=会话,Store作用域=单例,其中Store依赖于ShoppingCart,Spring创建Store单例并进行依赖注入时,没有ShoppingCart可用,因为还没有会话生成。这个时候怎么实现注入呢?

Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理。

这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。见下图。

image.png

图1 作用域代理能够延迟注入请求和会话作用域的bean

5、运行时值注入

  • 占位符: ${变量名}
  • SpEL表达式: #{...}

(三)Spring AOP

1、定义切点

定义一个方法,咱们准备从这个方法的执行去做切点

@Component
public class Performance {

    public void perform(){
        ...
    }
}

image.png 表2 Spring借助借 AspectJ的切点表达式语言来定义 的 Spring切面

2、定义切面

@Aspect
public class PerformanceAspect {

    @Before("execution(* Performance.perform())") // 查看1中【表2 Spring借助借 AspectJ的切点表达式语言来定义 的 Spring切面】
    public void before(){
        ...
    }

}

3、创建通知

(1)通知类型

image.png

@Aspect
public class PerformanceAspect {

    @Before("execution(* Performance.perform())")
    public void before(){
        ...
    }


    @After("execution(* Performance.perform())")
    public void after(){
        ...
    }


    @Around("execution(* Performance.perform())")
    public void around(ProceedingJoinPoint joinPoint){
        ...
        joinPoint.proceed();// 继续执行被拦截方法
         ...
    }

    @AfterThrowing("execution(* Performance.perform())")
    public void afterThrowing(){
        ...
    }

    @AfterReturning("execution(* Performance.perform())")
    public Object afterReturning(){
        ...
    }
}

(二)第二部分.使用Spring构建Web程序

(三)第三部分.如何处理和持久化数据

(四)第四部分.如何将Spring应用程序与其他系统进行集成