代理模式

代理模式

1. 代理模式

目标:理解什么是代理,以及它的作用

1.1 何为代理?

​ 代理是通过代理对象访问目标对象,这样可以在目标对象的逻辑进行增强,或者称之为改变。

​ 代理分为静态代理动态代理两种。

1.2 组成结构

​ 代理逻辑中一般包含一个目标对象和一个代理对象。这个怎么理解呢?

​ 举个例子:这就好比一部日本拍了一部小电影(demo.avi),但是电影可能还要加入一些马赛克才能发行,最后成为一部新的电影(demo2.avi),那这个demo.avi就相当于目标对象,而这个demo2.avi就相当于代理对象。demo2是对demo1内容的增强,或者称之为改变。

代码样例

  • 目标类:

  • package cn.itcast.show.inherit;
    
    /**
     * 目标类
     *
     * @Author LK
     * @Date 2020/9/30
     */
    public class HeimaService {
        public void test(){
            System.out.println("---目标类逻辑---");
        }
    }
    
  • 代理类:

    package cn.itcast.show.inherit;
    
    /**
     * 代理类
     *
     * @Author LK
     * @Date 2020/9/30
     */
    public class HeimaProxyService extends HeimaService{
    
        @Override
        public void test() {
            super.test();
            System.out.println("---代理类逻辑---");
        }
    }
    
  • 运行主类:

    package cn.itcast.show;
    
    /**
     * 运行主类
     *
     * @Author LK
     * @Date 2020/9/30
     */
    public class TestMain {
        public static void main(String[] args) {
            HeimaService heimaService = new HeimaProxyService();
            heimaService.test();
        }
    }
    
  • 运行结果:
    在这里插入图片描述
    从上面的结果不难看出,代理类对目标类的逻辑进行了改变,这就称之为代理。

1.3 为什么要代理?

​ 原因一:你可能没有目标类的源码,也就是java文件,这样你如何修改逻辑?

​ 原因二:若是直接在目标类上修改某方法逻辑,而项目中有多处都需要调用该类的该方法,会导致所有调用处的逻辑都发生改变,但假如我只想在某一处调用的地方添加自己的增强逻辑呢?

小结
  • 何为代理?代理就是通过代理对象对目标对象的代码逻辑进行增强/改变
  • 代理的作用?提高代码扩展性,本质上是设计模式的一中实现方式,对修改关闭,对扩展开放(开闭原则)

2. 静态代理

目标:能够说出静态代理和动态代理的区别,并掌握两种静态代理的代码实现。

2.1 介绍

​ 静态代理和动态代理都是实现代理的两种方式,区别在于:使用静态代理是需要编写代理类的,也就是最终你可以在你项目编译的文件中看到class文件,而动态代理是直接动态生成字节码文件(类加载器加载class文件到JVM中就变成了字节码文件)。

​ 实现静态代理又包含两种方式,分别是继承聚合

2.2 继承

上一章节中的样例就是使用的继承的方式,继承方式的核心思想是:使用代理类继承目标类,重写目标类的方法,并在重写的方法中调用目标类的方法,再添加代理的逻辑。

2.3 聚合

​ 通过聚合实现代理,一般有一个前提,就是代理类和目标类都实现同一个接口。我们看下面一个代码样例。

代码样例

  • 公用接口

  • package cn.itcast.show.aggregation;
    
    /**
     * 接口
     *
     * @Author LK
     * @Date 2020/9/30
     */
    public interface Service {
    
        /**
         * 接口方法
         */
        public void test();
    }
    
  • 目标类

    package cn.itcast.show.aggregation;
    
    /**
     * 目标类
     *
     * @Author LK
     * @Date 2020/9/30
     */
    public class TargetService implements Service{
    
        public void test() {
            System.out.println("---目标类逻辑---");
        }
    }
    
  • 代理类

    package cn.itcast.show.aggregation;
    
    /**
     * 代码类
     *
     * @Author LK
     * @Date 2020/9/30
     */
    public class ProxyService implements Service {
    
        // target为目标对象
        private Service target;
    
        public ProxyService(Service target) {
            this.target = target;
        }
    
        public void test() {
            // 调用目标对象的逻辑
            target.test();
            // 编写代理逻辑
            System.out.println("---代理类逻辑---");
        }
    }
    
    • 运行主类
    package cn.itcast.aggregation;
    
    import cn.itcast.show.juhe.ProxyService;
    import cn.itcast.show.juhe.Service;
    import cn.itcast.show.juhe.TargetService;
    
    /**
     * 运行主类
     *
     * @Author LK
     * @Date 2020/9/30
     */
    public class TestMain {
        public static void main(String[] args) {
            // HeimaService heimaService = new HeimaProxyService();
            // heimaService.test();
            Service service = new ProxyService(new TargetService());
            service.test();
        }
    }
    
  • 运行结果
    在这里插入图片描述

  • 聚合的核心思想是:代理类和目标类都实现同一个接口,创建代理对象时,通过构造方法或者set方法将目标对象聚合进去。

小结
  • 静态代理和动态代理的区别:有无生成class文件
  • 静态代理-继承方式:使用代理类继承目标类,重写目标类的方法,并在重写的方法中调用目标类的方法,再添加代理的逻辑。
  • 静态代理-聚合方式:代理类和目标类都实现同一个接口,创建代理对象时,通过构造方法或者set方法将目标对象聚合进去。

3. 动态代理

目标:JDK动态代理和cglib动态代理的区别,以及JDK动态代理的代码实现

3.1 介绍

​ 在前面的章节有讲到,动态代理即动态的生成字节码文件到JVM中,并不会看到class文件。动态代理又分为JDK动态代理和Cglib动态代理,两种的区别在于:JDK动态代理都是对接口进行代理,而Cglib一般是对类进行代理。

​ 值得一提的是,动态代理技术在开源框架中经常被使用到。例如Mybatis、还有Spring源码中也在很多地方使用了动态代理,像@Configuration注解,还有SpringCloud的Feign组件等等。

3.2 JDK动态代理代码
  • JdkService

  • package cn.itcast.show.jdk;
    
    /**
     * TODO
     *
     * @Author LK
     * @Date 2020/10/10
     */
    public interface JdkService {
        /**
         * 接口方法
         */
        public void test();
    }
    
  • MyInvocationHandler

    package cn.itcast.show.jdk;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * TODO
     *
     * @Author LK
     * @Date 2020/10/10
     */
    public class MyInvocationHandler implements InvocationHandler {
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("-----代理逻辑-----");
            return null;
        }
    }
    
  • TestMain

    package cn.itcast.show;
    
    import cn.itcast.show.jdk.JdkService;
    import cn.itcast.show.jdk.MyInvocationHandler;
    
    import java.lang.reflect.Proxy;
    
    /**
     * 运行主类
     *
     * @Author LK
     * @Date 2020/9/30
     */
    public class TestMain {
    
        public static void main(String[] args) {
            // HeimaService heimaService = new HeimaProxyService();
            // heimaService.test();
            // Service service = new ProxyService(new TargetService());
            // service.test();
            // 作用:为JdkService接口动态生成一个代理类,并加载到JVM,返回一个JdkService类型的代理对象
            JdkService o = (JdkService) Proxy.newProxyInstance(JdkService.class.getClassLoader(),
                    new Class[]{JdkService.class},
                    new MyInvocationHandler());
            // 实际上会执行MyInvocationHandler的invoke方法
            o.test();
        }
    }
    
  • 运行效果
    在这里插入图片描述

4. 模拟原生Mybatis

本章内容为模拟Mybatis的动态代理部分,其他类似于解析xml文件就不模拟了。

4.1 原生Mybatis使用

先来看看原生mybatis的用法,使用步骤如下:

  • 第一步:添加依赖

    <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>cn.itcast</groupId>
        <artifactId>itheima-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <!--mybatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.5</version>
            </dependency>
    
            <!--mysql驱动包-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
    
            <!--junit-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13</version>
                <scope>test</scope>
            </dependency>
            
    
        </dependencies>
    
    </project>
    
  • 第二步:resources目录下添加mybatis配置文件:mybatis-config.xml

  • <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/extend_db"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
    <!--    <mappers>-->
    <!--        <mapper resource="mappers/UserMapper.xml"/>-->
    <!--    </mappers>-->
        <mappers>
            <mapper class="cn.itcast.show.mockMybatis.UserMapper"></mapper>
        </mappers>
    
    </configuration>
    
  • 第三步:准备数据库数据,执行资料中的tb_user.sql文件

  • 第四步:编写实体类

  • package cn.itcast.show.mockMybatis;
    
    /**
     * 实体类
     *
     * @Author LK
     * @Date 2020/11/30
     */
    public class User {
    
        private Long id;
        private String name;
        private int age;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    
  • 第五步:编写mybatis接口

    package cn.itcast.show.mockMybatis;
    
    import org.apache.ibatis.annotations.Select;
    
    import java.util.List;
    
    /**
     * TODO
     *
     * @Author LK
     * @Date 2020/11/30
     */
    public interface UserMapper {
    
        @Select("select * from tb_user")
        public List<User> findList();
    }
    
    
    • 第六步:测试类
    package cn.itcast.show;
    
    import cn.itcast.show.mockMybatis.User;
    import cn.itcast.show.mockMybatis.UserMapper;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Test;
    
    import java.io.InputStream;
    import java.util.List;
    
    /**
     * TODO
     *
     * @Author LK
     * @Date 2020/11/30
     */
    public class MybatisTest {
    
        @Test
        public void testMybatis() throws Exception{
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
            List<User> userList = userMapper.findList();
            System.out.println("userList = " + userList);
        }
    }
    
  • 运行结果:在这里插入图片描述

以上,我们完成了原生mybatis的查询功能,在以下代码这行打断点,debug模式启动

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

debug步骤如下:
在这里插入图片描述
点击 Step Into
在这里插入图片描述
点击 Step Into
在这里插入图片描述
点击Step Over

在这里插入图片描述
再点击Step Into

在这里插入图片描述

4.2 模拟Mybatis动态代理
  • 第一步:编写mybatis的InvocationHandler类

    package cn.itcast.show.mockMybatis;
    
    import org.apache.ibatis.annotations.Select;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * TODO
     *
     * @Author LK
     * @Date 2020/11/30
     */
    public class MybatisInvocationHandler implements InvocationHandler {
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            List<User> userList = new ArrayList<User>();
    
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
            try {
                // 使用jdbc模拟,实际上Mybatis最底层仍然是使用jdbc
                Class.forName("com.mysql.jdbc.Driver");
                connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/extend_db",
                        "root", "123456");
                // 重点是获取sql语句,在userMapper的方法注解上
                Select annotation = method.getAnnotation(Select.class);
                String[] value = annotation.value();
                String sqlStr = value[0];
    
                preparedStatement = connection.prepareStatement(sqlStr);
                resultSet = preparedStatement.executeQuery();
    
                while(resultSet.next()){
                    User user = new User();
                    user.setId(resultSet.getLong("id"));
                    user.setName(resultSet.getString("name"));
                    user.setAge(resultSet.getInt("age"));
                    userList.add(user);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(connection!=null){
                    connection.close();
                }
                if(preparedStatement!=null){
                    preparedStatement.close();
                }
                if(resultSet!=null){
                    resultSet.close();
                }
            }
            return userList;
        }
    }
    
  • 第二步:编写测试类

  • package cn.itcast.show;
    
    import cn.itcast.show.mockMybatis.MybatisInvocationHandler;
    import cn.itcast.show.mockMybatis.User;
    import cn.itcast.show.mockMybatis.UserMapper;
    import org.junit.Test;
    
    import java.lang.reflect.Proxy;
    import java.util.List;
    
    /**
     * TODO
     *
     * @Author LK
     * @Date 2020/11/30
     */
    public class MockMybatisTest {
    
        @Test
        public void testMockMybatis(){
            // 得到代理对象
            UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(),
                    new Class[]{UserMapper.class},
                    new MybatisInvocationHandler());
    
            List<User> userList = userMapper.findList();
            System.out.println("userList = " + userList);
        }
    }
    
  • 第三步:运行结果:
    在这里插入图片描述

5. 如何生成Bean到Spring

整合Spring的关键点在于,将mybatis的接口类变成Bean,放到Spring容器当中。我们前面已经学过了好几种方式生成Bean了,我们简单回顾一下:

  • 方式一:spring的xml文件中,通过标签

  • 方式二:类上加入@Component注解或其子注解(@Service、@Controller、@Configuration等),然后通过@ComponentScan扫描。

  • 方式三:已有的Bean中,添加@Bean注解在方法上,把方法的返回值变成一个Bean。

  • 方式四:已有的Bean上,使用@Import注解,导入一个ImportSelector的实现类,在重写的selectImports方法中,返回的类全路径,会把返回值中的类变成Bean。

  • 方式五:已有的Bean上,使用@Import注解,导入一个ImportBeanDefinitionRegistrar的实现类,在重写的方法注册BD到Spring容器。

但是,无论是以上哪种方式都不可以,为什么呢?因为就把接口放到Spring容器,即使它能够变成一个Bean,但是依然不能创建对象,

因此,重点应该是把JDK动态代理得到的那个对象变成一个Bean,这里需要用到Spring一个知识点:FactoryBean

6. 模拟Mybatis集成Spring

加入spring依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.8.RELEASE</version>
</dependency>

编写UserMapperFactoryBean

package cn.itcast.show.mockMybatis;

import org.springframework.beans.factory.FactoryBean;

import java.lang.reflect.Proxy;

/**
 * userservice facotryBean
 *
 * @Author LK
 * @Date 2020/12/1
 */
public class UserMapperFactoryBean implements FactoryBean {

    public Object getObject() throws Exception {
        UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(),
                new Class[]{UserMapper.class},
                new MybatisInvocationHandler());

        return userMapper;
    }

    // 返回对象类型
    public Class<?> getObjectType() {
        return UserMapper.class;
    }

    // 是否单例
    public boolean isSingleton() {
        return true;
    }
}

编写主函数

package cn.itcast.show;

import cn.itcast.show.mockMybatis.UserMapper;
import cn.itcast.show.mockMybatis.UserMapperFactoryBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * TODO
 *
 * @Author LK
 * @Date 2020/11/30
 */
public class SpringMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);

        // 肯定不能在FactoryBean上加@Component注解,因为这种方式非常有局限性,spring怎么知道你的类在哪?
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        beanDefinition.setBeanClass(UserMapperFactoryBean.class);
        ac.registerBeanDefinition("userMapper", beanDefinition);

        // 虽然获取的是userMapperFactoryBean, 但是实际返回值却是该类中getObject返回的对象
        UserMapper userMapper = (UserMapper) ac.getBean("userMapper");
        System.out.println(userMapper.findList());

    }
}

修改配置类

package cn.itcast.show;

import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 * TODO
 *
 * @Author LK
 * @Date 2020/11/30
 */
@Configuration
public class Config {
}

运行结果

[User{id=1, name='张三', age=18}, User{id=2, name='李四', age=19}, User{id=3, name='王五', age=20}]

但是现在有个致命的缺陷,就是虽然把UserMapper变成了一个Bean,但在实际项目中,不可能只有一个UserMapper接口吧?如果有成百上千个Service接口,岂不是要写非常多XXXFactoryBean这样的类?因此我们需要优化一下代码,写一个公共的FactoryBean

创建公共FactoryBean:MybatisSpringFactoryBean

package cn.itcast.show.mockMybatis;

import org.springframework.beans.factory.FactoryBean;

import java.lang.reflect.Proxy;

/**
 * 公共的factoryBean
 *
 * @Author LK
 * @Date 2020/12/1
 */
public class MybatisSpringFactoryBean implements FactoryBean {

    // 代理的接口
    Class mapperInterface;

    public MybatisSpringFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Object getObject() throws Exception {
        return Proxy.newProxyInstance(mapperInterface.getClassLoader(),
                new Class[]{mapperInterface},
                new MybatisInvocationHandler());
    }

    public Class<?> getObjectType() {
        return mapperInterface;
    }

    public boolean isSingleton() {
        return false;
    }
}

修改主函数

package cn.itcast.show;

import cn.itcast.show.mockMybatis.MybatisSpringFactoryBean;
import cn.itcast.show.mockMybatis.UserMapper;
import cn.itcast.show.mockMybatis.UserMapperFactoryBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * TODO
 *
 * @Author LK
 * @Date 2020/11/30
 */
public class SpringMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);

        // 肯定不能在FactoryBean上加@Component注解,因为这种方式非常有局限性,spring怎么知道你的类在哪?
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        beanDefinition.setBeanClass(MybatisSpringFactoryBean.class);
        // 添加构造方法的参数
        beanDefinition.getConstructorArgumentValues()
                .addGenericArgumentValue("cn.itcast.show.mockMybatis.UserMapper");
        ac.registerBeanDefinition("userMapper", beanDefinition);

        UserMapper userMapper = (UserMapper) ac.getBean("userMapper");
        System.out.println(userMapper.findList());

    }
}

现在又有问题, 我的这个代码放在主函数里,太不优雅了,而且实际项目中我们也拿不到ApplicaitonContex,如何解决?Spring提供了一种方式给我们

编写一个ImportBeanDefinitionRegistrar的实现类:MyImportBeanDefinitionRegistrar

package cn.itcast.show.mockMybatisSpring;

import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

/**
 * TODO
 *
 * @Author LK
 * @Date 2020/12/1
 */
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        beanDefinition.setBeanClass(MybatisSpringFactoryBean.class);
        // 添加构造方法的参数
        beanDefinition.getConstructorArgumentValues()
                .addGenericArgumentValue("cn.itcast.show.mockMybatis.UserMapper");
        registry.registerBeanDefinition("userMapper", beanDefinition);

    }
}

自定义注解类:MyMapperScan,并且在上面加入Import注解

package cn.itcast.show.mockMybatisSpring;

import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyMapperScan {
    String[] value();
}

在配置类Config中添加@MyMapperScan注解

package cn.itcast.show;

import cn.itcast.show.mockMybatisSpring.MyMapperScan;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

/**
 * TODO
 *
 * @Author LK
 * @Date 2020/11/30
 */
@MyMapperScan("cn.itcast.show.mockMybatis")
@Configuration
public class Config {
    
}
package cn.itcast.show;

import cn.itcast.show.mockMybatisSpring.MyMapperScan;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

/**
 * TODO
 *
 * @Author LK
 * @Date 2020/11/30
 */
@MyMapperScan("cn.itcast.show.mockMybatis")
@Configuration
public class Config {
    
}

修改main函数

package cn.itcast.show;

import cn.itcast.show.mockMybatisSpring.MybatisSpringFactoryBean;
import cn.itcast.show.mockMybatis.UserMapper;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * TODO
 *
 * @Author LK
 * @Date 2020/11/30
 */
public class SpringMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);

        UserMapper userMapper = (UserMapper) ac.getBean("userMapper");
        System.out.println(userMapper.findList());

    }
}

我们之前讲过,Spring启动过程中,如果在已有的Bean上存在@Import注解,导入一个ImportBeanDefinitionRegistrar的实现类,会默认执行重写的方法,也就是registerBeanDefinitions方法。重新执行,依然是可以得到结果:

[User{id=1, name='张三', age=18}, User{id=2, name='李四', age=19}, User{id=3, name='王五', age=20}]

可是代码还不够完美,在MyImportBeanDefinitionRegistrar中是写死代码只注册UserMapper的,我们应该是需要把@MyMapperScan注解中的指定的包下面所有的接口都注册到Spring容器里,继续改造:

MyImportBeanDefinitionRegistrar

package cn.itcast.show.mockMybatisSpring;

import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

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

/**
 * TODO
 *
 * @Author LK
 * @Date 2020/12/1
 */
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 获取到注解的属性
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName());
        // 获取注解的value属性值
        String[] value = (String[]) attributes.get("value");
        if (null != value && value.length > 0) {
            // 得到包路径
            String packagePath = value[0];
            System.out.println("packagePath = " + packagePath);
            // 自定义扫描器,不能用原本spring的扫描器,因为会按照spring自己的逻辑去扫描,扫描不到
            MyClassPathBeanDefinitionScanner scanner = new MyClassPathBeanDefinitionScanner(registry);

            // 允许扫描
            scanner.addIncludeFilter(new TypeFilter() {
                public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                    return true;
                }
            });

            // 得到扫描到的beanDefinition包装对象集合
            Set<BeanDefinitionHolder> beanDefinitionHolders = scanner.doScan(packagePath);

            for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                // 注册到spring容器中
                BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, registry);
            }

        }else{
            System.out.println("未指定扫描包路径");
        }
    }
}

新增类:MyClassPathBeanDefinitionScanner

package cn.itcast.show.mockMybatisSpring;

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 java.util.Set;

/**
 * 自定义扫描器
 *
 * @Author LK
 * @Date 2020/12/1
 */
public class MyClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

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

    @Override
    // 扫描
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);

        // 父类扫描到的beanDefinition,不符合我们要求,我们需要改内容
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
            // 原本的className
            String beanClassName = beanDefinition.getBeanClassName();
            // 设置为FactoryBean的name
            beanDefinition.setBeanClassName(MybatisSpringFactoryBean.class.getName());
            // 添加构造方法的参数
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
        }
        return beanDefinitionHolders;
    }

    @Override
    // 只扫描接口
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface();
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值