专栏首页程序猿讲故事SpringBoot项目的代理机制【一】

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),作者:程序猿讲故事

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-01-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 制作mysql大数据表并验证覆盖索引的查询效率

    昨天跟同事聊起数据表性能的问题,能不能仅用覆盖索引实现数据的汇总统计。找了一个开发环境已有的数据表进行测试,通过explain命令,能看到mysql通过覆盖索引...

    程序猿讲故事
  • JSON金额解析BUG的解决过程

    这是在我们开发的一个支付系统中暴露的一个BUG,问题本身比较简单,有意思的是解决问题的过程。将过程分享出来,希望能够对大家有所帮助。

    程序猿讲故事
  • 多War项目中静态文件的共享方案

    在互联网产品中,一般会有多个项目(Jar、WAR)组成一个产品线。这些WAR项目,因为使用相同的前端架构(jQuery、easyui等),在各个项目中都会存在这...

    程序猿讲故事
  • Spring Boot 整合 docker

    Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括...

    程序员果果
  • SpringBoot集成Thymeleaf

    上一篇给大家介绍了springboot整合freemarker,这一片来继续为大家介绍一种模板thymeleaf。 首先在项目中增添thymeleaf依赖spr...

    dalaoyang
  • SpringBoot:Actuator 监控管理工程各项信息

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    微风-- 轻许--
  • Spring使用webjar

    这玩意很简单,但是我们第一次搞就是搞不成功,为什么呢?因为我们都用的是idea或者eclipse编译。webjar只能在maven上才能打包,所以在使用时,记得...

    ydymz
  • 从1G到5G,46年屏幕变迁下,富士康、苹果、三星、华为的浴火重生路

    三星率先开局,华为紧跟,折叠屏在2019年的初春彻底火了。只是这一代的屏幕改革,从一开始便是腥风血雨。

    镁客网
  • Spring Boot 整合Dubbo开发实战

    Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度...

    架构探险之道
  • SpringBoot非官方教程 | 第一篇:构建第一个SpringBoot工程

    简介 spring boot 它的设计目的就是为例简化开发,开启了各种自动装配,你不想写各种配置文件,引入相关的依赖就能迅速搭建起一个web工程。它采用的是建...

    方志朋

扫码关注云+社区

领取腾讯云代金券