手写Spring整合Jpa流程

在本篇文章中,我将手写模拟Spring整合Jpa流程。

项目搭建

项目依赖:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    <dependencies>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.32.Final</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>compile</scope>
        </dependency>
        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>

        <!-- Spring test  -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-bom</artifactId>
                <version>2020.0.14</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

配置类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.xin.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;


import javax.sql.DataSource;


/**
 * @author : ichigo-xin
 * @date 2023/7/18
 */
@Configuration
@EnableJpaRepositories(basePackages = "com.xin.repository")
@EnableTransactionManagement
@ComponentScan("com.xin")
public class SpringDataConfig {

    @Bean
    public DataSource druidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/springdata?serverTimezone=UTC");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        return druidDataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setGenerateDdl(true);
        adapter.setShowSql(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setDataSource(druidDataSource());
        factory.setPackagesToScan("com.xin.pojo");
        factory.setJpaVendorAdapter(adapter);
        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager(){
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return txManager;
    }

}

pojo类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Entity
@Table(name = "tb_customer")
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;

    @Column(name = "cust_name")
    private String custName;

    @Column(name = "cust_address")
    private String custAddress;
}

CustomerRepository:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.xin.repository;


import com.xin.pojo.Customer;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Component;

/**
 * @author: ichigo-xin
 * @create: 2023-07-18 00:47
 * @description:
 **/
//@Component
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long> {

}

application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.xin;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.xin.config.SpringDataConfig;
import com.xin.pojo.Customer;
import com.xin.repository.CustomerRepository;

/**
 * @author: ichigo-xin
 * @create: 2023-07-26 01:24
 * @description:
 **/
public class Application {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringDataConfig.class);
        CustomerRepository repository = context.getBean(CustomerRepository.class);
        Customer customer = repository.findById(1L).get();
        System.out.println(customer);
    }
}

手写模拟Spring Data Jpa

在配置类中使用@EnableJpaRepositories(basePackages = “com.xin.repository”)注解,就是开启spring data jpa,现在是可以查询出数据的。

我们把这个注解注释掉,此时运行报错:

No qualifying bean of type ‘com.xin.repository.CustomerRepository’ available

说明没有这个类型的bean,这时候我们就需要去找这个bean没有注入的原因。

问题原因

spring读取注册BeanDefinition的流程如下:需要创建BeanDefinition,再根据BeanDefinition创建对象(bean的创建就不继续了),我们在这几个类里面打上断点,调试看看。

在这两个红框的方法内,一个是判断类上面有没有@component注解,另一个是判断这个类是不是接口等

可以看到如果是一个接口,他就不会注册成beandefinition。

自定义扫描器将接口也注册成beandefinition

我们自定义扫描器,不排除掉接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.xin.my;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;

import java.io.IOException;
import java.util.Set;

/**
 * @author: ichigo-xin
 * @create: 2023-07-26 02:19
 * @description:
 **/
public class MyJpaClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

    public MyJpaClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return metadata.isInterface();
    }

}

再运行代码,就会报错:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.xin.repository.CustomerRepository]: Specified class is an interface

这说明CustomerRepository作为接口也注册成一个beandefinition了。只是不能进行实例化。在spring当中,每一个bean其实就是一个实例化的对象,那么接口肯定不能实例化的,这里我们就需要使用动态代理了。

自定义MyJpaProxy,实现JpaRepository接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.xin.my;

import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

/**
 * @author: ichigo-xin
 * @create: 2023-07-26 21:08
 * @description:
 **/
public class MyJpaProxy implements JpaRepository {

    EntityManager em;
    Class pojoClass;

    public MyJpaProxy(EntityManager em, Class pojoClass) {
        this.em = em;
        this.pojoClass = pojoClass;
    }

    @Override
    public Optional findById(Object id) {
        System.out.println("自定义JPA统一实现");
        return Optional.of(em.find(pojoClass, id));
    }

    ....

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.xin.my;

import javax.persistence.EntityManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author: ichigo-xin
 * @create: 2023-07-26 21:01
 * @description:
 **/
public class MyJpaInvocationHandler implements InvocationHandler {

    EntityManager em;

    Class pojoClass;

    public MyJpaInvocationHandler(EntityManager em, Class pojoClass) {
        this.em = em;
        this.pojoClass = pojoClass;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MyJpaProxy myJpaProxy = new MyJpaProxy(em, pojoClass);
        Method jpaMethod = myJpaProxy.getClass().getMethod(method.getName(), method.getParameterTypes());
        return jpaMethod.invoke(myJpaProxy, args);
    }
}

我们自定义MyJpaFactoryBean,在getObject方法里面使用Proxy.newProxyInstance()来创建对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.xin.my;

import com.xin.repository.CustomerRepository;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.stereotype.Component;

import javax.persistence.EntityManager;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;

/**
 * @author: ichigo-xin
 * @create: 2023-07-26 20:56
 * @description:
 **/

public class MyJpaFactoryBean implements FactoryBean {

    @Autowired
    LocalContainerEntityManagerFactoryBean entityManagerFactory;

    Class<?> repositoryInterface;

    public MyJpaFactoryBean(Class<?> repositoryInterface) {
        this.repositoryInterface = repositoryInterface;
    }

    @Override
    public Object getObject() throws Exception {
        EntityManager em = entityManagerFactory.createNativeEntityManager(null);

        //获取当前接口的pojo类
        ParameterizedType parameterizedType = (ParameterizedType) repositoryInterface.getGenericInterfaces()[0];
        Type type = parameterizedType.getActualTypeArguments()[0];
        Class clazz = Class.forName(type.getTypeName());

        return Proxy.newProxyInstance(
                CustomerRepository.class.getClassLoader(),
                new Class[]{repositoryInterface},
                new MyJpaInvocationHandler(em, clazz));
    }

    @Override
    public Class<?> getObjectType() {
        return repositoryInterface;
    }
}

接下来我们需要在容器加载BeanDefinition的时候将本来应该是实例化接口的步骤,替换成我们上面自定义的实现。

在 Spring 框架中,BeanDefinitionRegistryPostProcessor 是一个重要的扩展点接口,它允许开发者在容器加载 Bean 定义(BeanDefinition)之后,但在实例化 Bean 之前,对 Bean 定义进行自定义修改或添加新的 Bean 定义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.xin.my;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.data.repository.Repository;
import org.springframework.stereotype.Component;

/**
 * @author: ichigo-xin
 * @create: 2023-07-26 02:24
 * @description:
 **/
@Component
public class MyJpaBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        MyJpaClassPathBeanDefinitionScanner scanner = new MyJpaClassPathBeanDefinitionScanner(registry);
        scanner.addIncludeFilter(new AssignableTypeFilter(Repository.class));
        scanner.scan("com.xin.repository");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

同时给MyJpaClassPathBeanDefinitionScanner重写doscan方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.xin.my;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;

import java.io.IOException;
import java.util.Set;

/**
 * @author: ichigo-xin
 * @create: 2023-07-26 02:19
 * @description:
 **/
public class MyJpaClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

    public MyJpaClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return metadata.isInterface();
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            ScannedGenericBeanDefinition beanDefinition = (ScannedGenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
            String beanClassName = beanDefinition.getBeanClassName();
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            // 修改beanDefinition
            beanDefinition.setBeanClass(MyJpaFactoryBean.class);
        }
        return beanDefinitionHolders;
    }

}

我们在doscan方法中对beanDefinition的BeanClass进行替换。

运行代码,成功查询出来数据了。 同时我们也可以看下代理对象

Built with Hugo
主题 StackJimmy 设计