• SpringBoot项目的代理机制【一】


    这是了解Spring代理机制的第一篇,尝试了解Spring如何实现Bean的注册和代理。这篇文章会抛出问题:Spring注册Bean,都会用Jdk代理或cglib创建代理对象吗?

    1 项目准备

    1.1 创建 Spring Boot 项目

    创建一个使用 jpa 访问数据库的 Spring Boot 项目。

    1.1.1 pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/>
        </parent>
        <groupId>tech.codestory.research</groupId>
        <artifactId>research-spring-boot</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>research-spring-boot</name>
    
        <properties>
            <java.version>1.8</java.version>
            <fastjson.version>1.2.62</fastjson.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-core</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
    
            <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>  

    1.1.2 application.yml

    src/main/resources/application.yml

    server:
      port: 9080
    spring:
      datasource:
        driver-class-name: org.h2.Driver
        url: jdbc:h2:mem:h2test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
        username: sa
        password:
        platform: h2
      jpa:
        database-platform: org.hibernate.dialect.H2Dialect
        hibernate:
          ddl-auto: update
        properties:
          hibernate:
            show_sql: true
            use_sql_comments: tru
      h2:
        console:
          enabled: true
          path: /console
          settings:
            trace: false
            web-allow-others: false
    logging:
      level:
          root: INFO

    1.1.3 ResearchSpringBootApplication.java

    主程序

    package tech.codestory.research.boot;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * Research Spring Boot Demo Application
     *
     * @author javacodestory@gmail.com
     */
    @SpringBootApplication
    public class ResearchSpringBootApplication {
        public static void main(String[] args) {
            SpringApplication.run(ResearchSpringBootApplication.class, args);
        }
    } 

    1.2 监控 Spring 注册的 Bean

    为了方便了解 Spring 启动过程,先创建一个类用于在日志中输出生成的 Bean。可以使用 BeanPostProcessor 接口。它设计的作用,如果需要在 Spring 容器完成 Bean 的实例化、配置和其他的初始化前后添加一些自己的逻辑处理,就可以定义一个或者多个 BeanPostProcessor 接口的实现,然后注册到容器中。

    我们实现一个接口,只是打印一下注册的 Bean 信息,代码如下:

    package tech.codestory.research.boot.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.stereotype.Component;
    
    /**
     * 创建一个 BeanPostProcessor , 为了方便查看Spring 注册的 Bean
     *
     * @author javacodestory@gmail.com
     */
    @Component
    @Slf4j
    public class SpringBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName)
                throws BeansException {
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName)
                throws BeansException {
            log.info("完成 初始化Bean {} : {}", beanName, bean.getClass().getName());
            return bean;
        }
    }

    启动项目,在控制台就可以看到一些日志输出(对输出做了一些调整):

    完成 初始化Bean dataSource : com.zaxxer.hikari.HikariDataSource
    完成 初始化Bean entityManagerFactoryBuilder : org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder
    完成 初始化Bean researchSpringBootApplication : tech.codestory.research.boot.ResearchSpringBootApplication$$EnhancerBySpringCGLIB$$e4d04c1b
    完成 初始化Bean transactionManager : org.springframework.orm.jpa.JpaTransactionManager
    完成 初始化Bean jdbcTemplate : org.springframework.jdbc.core.JdbcTemplate
    完成 初始化Bean namedParameterJdbcTemplate : org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate

    注意看bean:researchSpringBootApplication,实例类名中有字符串$$EnhancerBySpringCGLIB$$,这表示 Spring 使用 cglib 实现其代理类。

    2 创建一个基本的 Service

    在代码中创建一个 service,观察 Spring 注册 Bean 的信息。

    2.1 数据对象 model

    package tech.codestory.research.boot.model;
    
    import lombok.Data;
    
    /**
     * 用户实体
     *
     * @author javacodestory@gmail.com
     */
    @Data
    public class UserInfo {
        /**
         * 账号
         */
        private String account;
        /**
         * 密码
         */
        private String password;
        /**
         * 姓名
         */
        private String name;
    }  

    2.2 service 接口

    package tech.codestory.research.boot.service;
    
    import tech.codestory.research.boot.model.UserInfo;
    
    /**
     * 定义 Service 接口
     *
     * @author javacodestory@gmail.com
     */
    public interface UserInfoFirstService {
        /**
         * 获取一个用户信息
         *
         * @param account
         * @return
         */
        UserInfo getUserInfo(String account);
    }

    2.3 无其他注解的 service 实现

    package tech.codestory.research.boot.service.impl;
    
    import org.springframework.stereotype.Service;
    import tech.codestory.research.boot.model.UserInfo;
    import tech.codestory.research.boot.service.UserInfoFirstService;
    
    /**
     * 没有添加其他注解的实现类
     *
     * @author javacodestory@gmail.com
     */
    @Service
    public class UserInfoFirstServiceImpl implements UserInfoFirstService {
        /**
         * 获取一个用户信息
         *
         * @param account
         * @return
         */
        @Override
        public UserInfo getUserInfo(String account) {
            return null;
        }
    }

    2.4 查看 Bean 注册信息

    重新启动项目,再日志中查看 Bean 注册信息,可以看到注册的 beanName 是 userInfoFirstServiceImpl

    完成 初始化Bean userInfoFirstServiceImpl : tech.codestory.research.boot.service.impl.UserInfoFirstServiceImpl

    2.5 测试 Bean 引用

    2.5.1 测试类 UserInfoFirstServiceTest

    注意,我在测试代码中同时注入了 UserInfoFirstService 和 UserInfoFirstServiceImpl

    package tech.codestory.research.boot.service;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import tech.codestory.research.boot.service.impl.UserInfoFirstServiceImpl;
    
    /**
     * 测试UserInfoFirstService
     *
     * @author javacodestory@gmail.com
     */
    @SpringBootTest
    @Slf4j
    public class UserInfoFirstServiceTest {
        @Autowired
        UserInfoFirstService firstService;
        @Autowired
        UserInfoFirstServiceImpl firstServiceImpl;
    
        @Test
        public void testServiceInstances() {
            log.info("firstService = {}", firstService);
            assert firstService != null;
            log.info("firstServiceImpl = {}", firstServiceImpl);
            assert firstServiceImpl != null;
    
            // 是同一个实例
            log.info("firstService 和 firstServiceImpl 是同一个Bean  = {}", firstService == firstServiceImpl);
            assert firstService == firstServiceImpl;
        }
    }

    2.5.2 测试结果

    在项目目录执行 maven 命令

    mvn clean test

    关键测试输出

    t.c.r.b.s.UserInfoFirstServiceTest       : firstService = tech.codestory.research.boot.service.impl.UserInfoFirstServiceImpl@4899799b
    t.c.r.b.s.UserInfoFirstServiceTest       : firstServiceImpl = tech.codestory.research.boot.service.impl.UserInfoFirstServiceImpl@4899799b
    t.c.r.b.s.UserInfoFirstServiceTest       : firstService 和 firstServiceImpl 是同一个Bean  = true
    [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.01 s - in tech.codestory.research.boot.service.UserInfoFirstServiceTest

    从测试结果看,两个依赖注入都正常引用了同一个对象。

    2.6 问题来了

    2.6.1 问题 1

    通常我们理解Spring 注册Bean,会使用JDK代理或cglib。但在本例中,注册UserInfoFirstServiceImpl 时,为什么没有创建代理对象?

    2.6.2 问题 2

    注册 beanName 是userInfoFirstServiceImpl,为什么用接口和实现类定义变量却都能正常注入?

    【未完待续】续篇不知要到猴年马月 

    敬请关注公众号 《程序猿讲故事》  codestory 

  • 相关阅读:
    在Ubuntu下安装软件
    HIVE Thrift Server使用总结
    用Wubi来安装Ubuntu 12
    linux命令总结
    Linux Sed简介
    Linux批量重命名文件
    算法学习的轨迹(转)
    写入es速率优化
    走近Flex组件系列(三):按扭组件(Button,CheckBox,LinkBar,LinkButton,PopUpButton,RadioButton,ToggleButtonBar)
    走近Flex组件系列(二):简单实用的Alert组件
  • 原文地址:https://www.cnblogs.com/codestory/p/12161570.html
Copyright © 2020-2023  润新知