1-Spring 简单使用

简单使用#

第一步:创建一个 maven 项目

第二步:引入 Spring 依赖

<properties>
    <spring.version>5.1.1.RELEASE</spring.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>

第三步:新建一个配置类

// 告诉Spring这是一个配置类
@Configuration
public class MainConfig {
    // 给容器中注册一个Bean, id 默认使用方法名. 也可以自定义指定id, 通过@Bean("person")
    @Bean
    public Person person() {
        return new Person();
    }
}

第四步:创建一个测试类

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        // 从IOC容器中获取id为person的对象
        Person person = (Person) context.getBean("person");
        System.out.println(person);
    }
}

常用注解#

@ComponentScan#

启用组件扫描,默认会扫描与配置类相同的包。可以通过设置 basePackages 属性来指定扫描路径,如 @ComponentScan(basePackages = {"package1","package2"})。扫描的时候查找 package1package2 包下标注了 @Controller@Service@Repository@Component 的类 (默认情况下,这些类注入到 IOC 容器的 id 为首字母小写的类名),这些类都会被注入到 IOC 容器中。

// 告诉Spring这是一个配置类
@Configuration
// 包扫描, 只要标注了@Controller @Service @Repository @Component的类, 这些类都会被注入到IOC容器中
@ComponentScan(basePackages = {"com.example.spring.sourcecode"})
public class MainConfig {
    // 给容器中注册一个Bean, id 默认使用方法名. 也可以自定义指定id, 通过@Bean("person")
    @Bean
    public Person person() {
        return new Person();
    }
}

@Autowired#

自动注入实例。Spring 容器中匹配的时候 Bean 数目必须有且仅有一个。当找不到一个匹配的 Bean 时,Spring 容器将抛出 BeanCreationException 异常,并指出必须至少拥有一个匹配的 Bean。

@Autowired 默认是按照 byType 进行注入的,如果发现找到多个 bean,则又按照 byName 方式比对,如果还有多个,则报出异常。@Autowired 可以手动指定按照 byName 方式注入,使用 @Qualifier 标签指定 bean 的名称。

@Primary#

如果容器中注入了多个类型一样的 Bean,自动注入时会优先注入被 @Primary 注解标注的 Bean

@Import#

它可以将多个分散的 @Configuration 注入到 Spring 容器中。

用法一

@Configuration
@Import({MainConfig1.class,MainConfig2.class})
public class MainConfig {

}

用法二

Import 结合 ImportSelector 接口

// MyImportSelect.class
public class MyImportSelect implements ImportSelector {
    /**
     * @param annotationMetadata 当前标注 @Import注解的所有注解信息
     * @return 返回值就是导入到容器中的全类名
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{Red.class.getName(), Orange.class.getName()};
    }
}

// MainConfig.class
@Configuration
@Import({MyImportSelect.class})
public class MainConfig {

}

用法三

结合 ImportBeanDefinitionRegistrar 接口

// MyImportBeanDefinitionRegistrar.class
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * @param annotationMetadata
     * @param beanDefinitionRegistry 通过这个参数可以操作IOC容器
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        BeanDefinitionBuilder yellow = BeanDefinitionBuilder.rootBeanDefinition(Yellow.class);
        // 往容器中注册对象
        beanDefinitionRegistry.registerBeanDefinition("yellow", yellow.getBeanDefinition());

        BeanDefinitionBuilder green = BeanDefinitionBuilder.rootBeanDefinition(Green.class);
        // 往容器中注册对象
        beanDefinitionRegistry.registerBeanDefinition("green", green.getBeanDefinition());
    }
}

// MainConfig.class
@Configuration
@Import({MyImportSelect.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig {

}

@Conditional#

按照一定的条件给容器注册 bean。

假设现在有一个名为 WindowsBean 的类,我们希望只有当前操作系统是 Windows 的时候才会将其注入到 IOC 容器中。

@Configuration
@ComponentScan(basePackages = {"com.example.spring.sourcecode"})
public class MainConfig {
    @Bean
    @Conditional(WindowsCondition.class)
    public WindowsBean windowsBean() {
        return new WindowsBean();
    }
}

可以看到,@Conditional 中给定了一个 Class,它指明了条件。

public class WindowsCondition implements Condition {
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        String osName = environment.getProperty("os.name");
        return osName.contains("Windows");
    }
}

matches 方法有一个 ConditionContext 参数,ConditionContext 是一个接口

public interface ConditionContext {
    BeanDefinitionRegistry getRegistry();
    @Nullable
    ConfigurableListableBeanFactory getBeanFactory();
    Environment getEnvironment();
    ResourceLoader getResourceLoader();
    @Nullable
    ClassLoader getClassLoader();
}

通过 ConditionContext 接口我们可以做到如下几点

  1. 借助 getRegistry () 返回的 BeanDefinitionRegistry 检查 bean 定义
  2. 借助 getBeanFactory () 返回的 ConfigurableListableBeanFactory 检查 bean 是否存在,甚至可以查看 bean 的属性
  3. 借助 getEnvironment () 返回的 Environment 检查环境变量是否存在以及它的值是什么
  4. 借助 getClassLoader () 返回的 ClassLoader 加载并检查类是否存在

@Value#

使用 @Value 进行赋值

1. 基本数值

2. 可以写 SpEL, #{}

3. 可以写 ${},取出配置文件中的值 (在运行环境中)

public class Person {
    // @Value("张三")
    @Value("${app.name:lzc}") // 如果app.name不存在,默认值为lzc
    private String username;
    // @Value("10")
    @Value("${app.age:16}")
    private Integer age;
}

有两种方式将 app.name 和 app.age 注入到运行环境中

第一种:程序运行时指定运行时环境变量的值

-Dapp.name=zhencheng -Dapp.age=12

第二种:引入配置文件

新建一个 config.properties 配置文件,内容如下

app.name=lizhencheng
app.age=18

在配置类上使用 @PropertySource({"config.properties"}) 注解来引入配置文件

如果第一种方法和第二种方法都使用了,那么第一种方法的值会将第二种方法的值覆盖掉。

Aware 接口#

Spring 容器在初始化主动检测当前 bean 是否实现了 Aware 接口,如果实现了则回调其 set 方法将相应的参数设置给该 bean ,这个时候该 bean 就从 Spring 容器中取得相应的资源。

org.springframework.beans.factory.Aware 接口,定义如下:

public interface Aware {

}

Aware 接口为 Spring 容器的核心接口,是一个具有标识作用的超级接口,实现了该接口的 bean 是具有被 Spring 容器通知的能力,通知的方式是采用回调的方式。

Aware 接口是一个空接口,实际的方法签名由各个子接口来确定,且该接口通常只会有一个接收单参数的 set 方法,该 set 方法的命名方式为 set + 去掉接口名中的 Aware 后缀,即 XxxAware 接口,则方法定义为 setXxx (),例如 BeanNameAware(setBeanName),BeanFactoryAware(setBeanFactory)。

Spring 提供了一系列的 Aware 接口,如 BeanClassLoaderAware、BeanFactoryAware、BeanNameAware。

下面以 BeanFactoryAware 做一个简单的演示:

@Component
public class MyApplicationAware implements BeanFactoryAware {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("调用了 ApplicationContextAware 的 setBeanFactory 方法");
    }
}

通过这种方式 我们就可以将 ApplicationContext 注入到 MyApplicationAware 中。

BeanPostProcessor 接口#

在 Bean 完成实例化后,如果我们需要对其进行一些配置、增加一些自己的处理逻辑,就可以使用 BeanPostProcessor。

定义一个类,该类实现 BeanPostProcessor 接口,代码如下

@Component
public class BeanPostProcessorTest implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean [" + beanName + "] 开始初始化");
        // 这里一定要返回 bean,不能返回 null
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean [" + beanName + "] 完成初始化");
        return bean;
    }
}

测试方法

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
    }
}

控制台打印

Bean [mainConfig] 开始初始化
Bean [mainConfig] 完成初始化
调用了 ApplicationContextAware 的 setApplicationContext 方法
Bean [myApplicationAware] 开始初始化
Bean [myApplicationAware] 完成初始化

BeanPostProcessor 可以理解为是 Spring 的一个工厂钩子(其实 Spring 提供一系列的钩子,如 Aware 、InitializingBean、DisposableBean),它是 Spring 提供的对象实例化阶段强有力的扩展点,允许 Spring 在实例化 bean 阶段对其进行定制化修改,比较常见的使用场景是处理标记接口实现类或者为当前对象提供代理实现(例如 AOP)。

InitializingBean 和 DisposableBean 接口#

Spring 的 org.springframework.beans.factory.InitializingBean 接口,为 bean 提供了定义初始化方法的方式,它仅包含了一个方法:#afterPropertiesSet() 。代码如下:

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

org.springframework.beans.factory.DisposableBean 接口提供了对象的自定义销毁工作,它仅仅包含了一个方法:#destroy()。代码如下

public interface DisposableBean {
    void destroy() throws Exception;
}

示例

// InitializingBeanTest.class
public class InitializingBeanTest implements InitializingBean, DisposableBean {
    private String name;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBeanTest initializing...");
        this.name = "张三";
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("InitializingBeanTest destroy...");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

// 配置bean
@Bean
InitializingBeanTest initializingBeanTest() {
    InitializingBeanTest test = new InitializingBeanTest();
    test.setName("李四");
    return test;
}

// 测试类
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        InitializingBeanTest initializingBeanTest = (InitializingBeanTest)context.getBean("initializingBeanTest");
        System.out.println(initializingBeanTest.getName());
        ((AnnotationConfigApplicationContext) context).close();
    }
}

控制台打印

InitializingBeanTest initializing...
张三
InitializingBeanTest destroy...

在这个示例中,改变了 InitializingBeanTest 示例的 name 属性,也就是说 在 #afterPropertiesSet() 方法中,我们是可以改变 bean 的属性的,这相当于 Spring 容器又给我们提供了一种可以改变 bean 实例对象的方法。

BeanFactoryPostProcessor 接口#

BeanFactoryPostProcessor 的机制,就相当于给了我们在 Bean 实例化之前最后一次修改 BeanDefinition 的机会,我们可以利用这个机会对 BeanDefinition 来进行一些额外的操作,比如更改某些 bean 的一些属性,给某些 Bean 增加一些其他的信息等等操作。

org.springframework.beans.factory.config.BeanFactoryPostProcessor 接口,定义如下:

@FunctionalInterface
public interface BeanFactoryPostProcessor {
    /**
     * Modify the application context's internal bean factory after its standard
     * initialization. All bean definitions will have been loaded, but no beans
     * will have been instantiated yet. This allows for overriding or adding
     * properties even to eager-initializing beans.
     * @param beanFactory the bean factory used by the application context
     * @throws org.springframework.beans.BeansException in case of errors
     */
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

BeanFactoryPostProcessor 接口仅有一个 #postProcessBeanFactory(...) 方法,该方法接收一个 ConfigurableListableBeanFactory 类型的 beanFactory 参数。

#postProcessBeanFactory(...) 方法,工作于 BeanDefinition 加载完成之后,Bean 实例化之前,其主要作用是对加载 BeanDefinition 进行修改。有一点需要需要注意的是在 #postProcessBeanFactory(...) 方法中,千万不能进行 Bean 的实例化工作,因为这样会导致 Bean 过早实例化,会产生严重后果,我们始终需要注意的是 BeanFactoryPostProcessor 是与 BeanDefinition 打交道的,如果想要与 Bean 打交道,请使用 BeanPostProcessor

与 BeanPostProcessor 一样,BeanFactoryPostProcessor 同样支持排序,一个容器可以同时拥有多个 BeanFactoryPostProcessor ,这个时候如果我们比较在乎他们的顺序的话,可以实现 Ordered 接口。

如果要自定义 BeanFactoryPostProcessor ,直接实现该接口即可。

示例如下所示

// 配置Person信息
@Bean
public Person person() {
    Person person = new Person();
    person.setUsername("张三");
    person.setAge(10);
    return person;
}

@Component
public class BeanFactoryPostProcessor_1 implements BeanFactoryPostProcessor,Ordered {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("调用 BeanFactoryPostProcessor_1 ...");
        // 获取指定的 BeanDefinition
        BeanDefinition bd = beanFactory.getBeanDefinition("person");
        MutablePropertyValues pvs = bd.getPropertyValues();
        pvs.addPropertyValue("username","lizhencheng");
    }
    @Override
    public int getOrder() {
        return 1;
    }
}


@Component
public class BeanFactoryPostProcessor_2 implements BeanFactoryPostProcessor,Ordered {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("调用 BeanFactoryPostProcessor_2 ...");
        // 获取指定的 BeanDefinition
        BeanDefinition bd = beanFactory.getBeanDefinition("person");
        MutablePropertyValues pvs = bd.getPropertyValues();
        pvs.addPropertyValue("age","18");
    }
    @Override
    public int getOrder() {
        return 1;
    }
}

提供了两个自定义的 BeanFactoryPostProcessor ,都继承 BeanFactoryPostProcessor 和 Ordered,其中 BeanFactoryPostProcessor_1 改变 username 的值,BeanFactoryPostProcessor_2 该变 age 的值。Ordered 分别为 1 和 2 。

测试类

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = (Person)context.getBean("person");
        System.out.println(person.toString());
    }
}

控制台输出

调用 BeanFactoryPostProcessor_1 ...
调用 BeanFactoryPostProcessor_2 ...
Person{username='lizhencheng', age=18}

看到运行结果,就能理解运行流程了

ApplicationListener 接口#

用来监听事件的发生,Spring 提供了如下事件类型

ApplicationContextEvent
ContextClosedEvent
ContextRefreshedEvent
ContextStartedEvent
ContextStoppedEvent
PayloadApplicationEvent

示例

@Component
public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("MyApplicationListener..." + event);
    }
}

Bean 的生命周期#

  1. Spring 对 bean 进行实例化
  2. Spring 将值和 bean 的引用注入到 bean 对应的属性中
  3. 如果 bean 实现了 BeanNameAware 接口,Spring 将 bean 的 ID 传递给 setBeanName () 方法
  4. 如果 bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory (BeanFactory beanFactory) 方法,将 BeanFactory 实例传入
  5. 如果 bean 实现了 BeanClassLoaderAware 接口,Spring 将调用 setBeanClassLoader (ClassLoader classLoader) 方法,传入 ClassLoader 实例
  6. 如果 bean 实现了 BeanPostProcessor 接口,Spring 将会调用它的 postProcessBeforeInitialization (Object bean, String beanName) 方法
  7. 如果 bean 实现了 InitializingBean 接口,Spring 将调用它的 afterPropertiesSet () 方法
  8. 如果 bean 实现了 BeanPostProcessor 接口,Spring 将会调用它的 postProcessAfterInitialization (Object bean, String beanName) 方法
  9. 此时,bean 已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文,直到该应用上下文被销毁
  10. 如果 bean 实现了 DisposableBean 接口,Spring 将会调用它的 destory () 方法

面向切面#

依赖如下

<properties>
    <spring.version>5.1.1.RELEASE</spring.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>

新建一个类

@Component
public class MathCalculator {
    public int div(int i, int j) {
        System.out.println("MathCalculator.div: i = " + i + ", j = " + j);
        return i/j;
    }
}

新建一个切面类

@Aspect // 告诉Spring这是一个切面类
@Component
public class LogAspects {

    // 切入点表达式
    @Pointcut("execution(* com.example.spring.sourcecode.aop.MathCalculator.*(..))")
    public void pointcut(){
    }

    // 目标方法运行之前运行
    @Before("pointcut()")
    public void logStart(JoinPoint joinPoint) {
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("【@Before】" + joinPoint.getSignature().getName() + ", args = " + args);
}
    // 目标方法运行结束后运行(无论方法正常结束还是异常结束都会调用)
    @After("pointcut()")
    public void logEnd(JoinPoint joinPoint) {
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("【@After】" + joinPoint.getSignature().getName() + ", args = " + args);
    }
    // 目标方法正常返回后运行
    @AfterReturning(value = "pointcut()", returning = "result") // returning 设置返回值使用result来接收
    public void logReturn(JoinPoint joinPoint, Object result) { // 这里的 JoinPoint 一定要在第一个参
        System.out.println("【@AfterReturning】" + joinPoint.getSignature().getName() + ", return = " + result);
    }
    // 目标方法异常后运行
    @AfterThrowing(value = "pointcut()", throwing = "exception") // throwing 设置异常信息使用exception来接收
    public void logException(JoinPoint joinPoint, Exception exception) {
        System.out.println("【@AfterThrowing】" + joinPoint.getSignature().getName() + ", Exception = " + exception.getMessage());
    }

    // 这个方法的想过和前面方法小效果是一样的
//    @Around("pointcut()")
//    public Object logAround(ProceedingJoinPoint jp) {
//        Object result = null;
//        try {
//            System.out.println("【Before】" + jp.getSignature().getName());
//            result = jp.proceed();
//            System.out.println("【After】" + jp.getSignature().getName());
//            System.out.println("【Return】" + result);
//        } catch (Throwable throwable) {
//            System.out.println("【Exception】" + jp.getSignature().getName());
//        }
//        return result;
//    }
}

配置类

@Configuration
@ComponentScan(basePackages = {"com.example.spring.sourcecode"})
@EnableAspectJAutoProxy // 开启基于注解的AOP模式
public class MainConfigOfConfig {

}

测试类

public class AOPTest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfConfig.class);
        MathCalculator mathCalculator = context.getBean(MathCalculator.class);
        System.out.println(mathCalculator.div(10,2));
    }
}

事物#

依赖

<properties>
        <spring.version>5.1.1.RELEASE</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    </dependencies>

配置类

/**
 * Created by lzc
 * 2020/3/3 22:24
 * 1. @EnableTransactionManagement // 开启事物管理器
 * 2. 配置事物管理器
 * 3. 给方法上加上@Transactional注解, 表示当前方法是一个事物方法
 */
@EnableTransactionManagement // 开启事物管理器
@Configuration
@ComponentScan(basePackages = {"com.example.spring.sourcecode.tx"})
public class TxConfig {

    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    // 注册事物管理器
    @Bean
    public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

UserDao 和 UserService

@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Transactional // 表示当前方法是一个事物方法
    public void insertUser() {
        String sql = "INSERT INTO user(username,age)VALUES(?,?)";
        String username = UUID.randomUUID().toString().substring(0,5);
        jdbcTemplate.update(sql,username,18);
        // 测试事物
        int a = 10/0;
    }
}

@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    public void insertUser() {
        userDao.insertUser();
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
        UserService userService = context.getBean(UserService.class);
        userService.insertUser();
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接