• Spring IOC概念与实现(注解方式)


    Spring 框架核心源码

    1、使用 Spring 框架

    2、反射机制

    IoC 控制反转 Inverse of Control 创建对象的权限,Java 程序中需要用到的对象不再由程序员自己创建,而是交给 IoC 容器来创建。

    前置要求:强大的反射和注解基础,可以参考:https://blog.csdn.net/jsdoulaoula/article/details/125529609

    1. 准备工作

    1、pom.xml

    
    <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.0modelVersion>
    
        <groupId>com.canruigroupId>
        <artifactId>SpringartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <properties>
            <maven.compiler.source>17maven.compiler.source>
            <maven.compiler.target>17maven.compiler.target>
        properties>
        <dependencies>
            
            <dependency>
                <groupId>javax.servletgroupId>
                <artifactId>javax.servlet-apiartifactId>
                <version>4.0.1version>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>1.18.20version>
            dependency>
        dependencies>
    
        
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.pluginsgroupId>
                    <artifactId>maven-compiler-pluginartifactId>
                    <version>3.10.1version>
                    <configuration>
                        <source>17source>
                        <target>17target>
                        <encoding>UTF-8encoding>
                    configuration>
                plugin>
            plugins>
        build>
    
    
        <packaging>warpackaging>
    
    project>
    
    • 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

    2、创建 Servlet

    package com.southwind.servlet;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @WebServlet("/hello")
    public class HelloServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.getWriter().write("Spring");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3、部署到 Tomcat

    在这里插入图片描述

    4、Servlet、Service、Dao

    Servlet–相当于controller层

    @WebServlet("/hello")
    public class HelloServlet extends HttpServlet {
    
        HelloService helloService = new HelloServiceImpl();
    
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.getWriter().write(helloService.findAll().toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Service

    public interface HelloService {
        public List<String> findAll();
    }
    
    public class HelloServiceImpl implements HelloService {
    
        HelloDao helloDao = new HelloDaoImpl();
        @Override
        public List<String> findAll() {
            return helloDao.findAll();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Dao

    public interface HelloDao {
        public List<String> findAll();
    }
    
    public class HelloDaoImpl implements HelloDao {
        @Override
        public List<String> findAll() {
            return Arrays.asList("1","2","3");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    最终可以在http://localhost:8080/hello得到
    在这里插入图片描述

    2. IOC核心概念

    当需求发生变更的时候(比如此时要求用HelloDaoImpl2),可能需要频繁修改 Java 代码,效率很低,如何解决?-----这里最主要的问题在于HelloDao helloDao = new HelloDaoImpl(); 和 HelloService helloService = new HelloServiceImpl(); 增加了耦合。

    HelloDaoImpl2

    public class HelloDaoImpl2 implements HelloDao {
        @Override
        public List<String> findAll() {
            return Arrays.asList("can","rui");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.1 雏形

    静态工厂

    public class BeanFactory {
        public static HelloDao getDao(){
            return new HelloDaoImpl();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    private HelloDao helloDao = BeanFactory.getDao();
    
    • 1

    上述的方式并不能解决我们的问题,需求发生改变的时候,仍然需要修改代码,怎么做到不改 Java 代码,就可以实现实现类的切换呢?

    外部配置文件的方式(动态生成—肯定需要反射)

    将具体的实现类写到配置文件中,Java 程序只需要读取配置文件即可。XML、YAML、Properties、JSON。maven工程需要把配置文件写到resources目录下

    1、定义外部配置文件,XML 配置文件需要通过 DOM 或 SAX 方式解析,而读取 properties 配置文件就比较容易

    hello = com.canrui.dao.impl.HelloDaoImpl
    
    • 1

    在这里插入图片描述

    2、让Java 程序读取这个配置文件。

    我们采用基于ClassLoder读取配置文件。该方式只能读取类路径下的配置文件,有局限,但是如果配置文件在类路径下比较方便。

    我们都知道java程序写好以后是以.java(文本文件)的文件存在磁盘上,然后,我们通过(bin/javac.exe)编译命令把.java文件编译成.class文件(字节码文件),并存在磁盘上。
    但是程序要运行,首先一定要把.class文件加载到JVM内存中才能使用的,我们所讲的classLoader,就是负责把磁盘上的.class文件加载到JVM内存中

    public class BeanFactory {
    
        private static Properties properties;
        //静态代码块在类加载时执行,并且只执行一次。
        //静态代码块在一个类中可以编写多个,并且遵循自上而下的顺序依次执行。
    
        //静态代码块的作用是什么?怎么用?用在哪儿?什么时候用?
        //静态代码块是java为程序员准备一个特殊的时刻这个特殊的时刻被称为类加载时刻。若希望在此刻执行一段特殊的程序,这段程序可以直接放在静态代码块当中。
        //通常在静态代码块当中完成预备工作,先完成数据的准备工具,例如:初始化连接池,解析XML配置文件
        static {
            properties = new Properties();
            try {
                // 使用ClassLoader加载properties配置文件生成对应的输入流
                // 使用properties对象加载输入流
                // 这样我们就把配置文件映射成为了一个对象
                properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static Object getDao() {
            // 获取key对应的value值
            String value = properties.getProperty("helloDao");
            // 获取到全类名:com.canrui.dao.impl.HelloDaoImpl 后根据反射去创建对象
            Object object = null;
            try {
                Class clazz = Class.forName(value);
                object = clazz.newInstance();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return object;
        }
    }
    
    • 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

    这里要注意下,我们返回的是object,不能在这里强转成HelloDao,否则还是写死了,因为这是工厂,不知道properties会产出哪个。所以工厂一定是父类,只有定成父类,什么样的Dao就都可以返回了

    我们在Service内强转。

    3、修改 Service

    private HelloDao helloDao = (HelloDao) BeanFactory.getDao();
    
    • 1

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-igbamnWx-1662261712458)(/Users/wangxinnan/Library/Application Support/typora-user-images/image-20220903115412972.png)]

    我们也只需要修改properties的配置文件,就可以更改

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o4WzeP4E-1662261712458)(/Users/wangxinnan/Library/Application Support/typora-user-images/image-20220903115500366.png)]

    2.2 修改雏形中的问题

    问题描述:Spring IoC 中的 bean 是单例,但我们目前实现的(见下图)

    public class HelloServiceImpl implements HelloService {
    
        private HelloDao helloDao = (HelloDao) BeanFactory.getDao();
    		// 在空参构造器里面循环10次调用
        public HelloServiceImpl() {
            for (int i = 0; i < 10; i++) {
                System.out.println(BeanFactory.getDao());
            }
        }
    
        @Override
        public List<String> findAll() {
            return helloDao.findAll();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    实际上,我们返回的Dao对象是10个完全不同的对象。我们通过反射创建了10个

    在这里插入图片描述

    那么有个问题:spring的bean为什么是单例的?我就要多例不行吗

    答:为了提高性能。

    由于不会每次都新创建新对象,所以就减少了新生成实例的消耗。因为spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法。

    减少JVM垃圾回收,由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。

    可以快速获取到bean,因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快。

    缺点就是在并发环境下可能会出现线程安全问题

    接下来进行优化–使用缓存

    private static Map cache = new HashMap<>();

    public class BeanFactory {
    
        private static Properties properties;
        // 单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快
        private static Map<String, Object> cache= new HashMap();
    
        static {
            properties = new Properties();
            try {
                properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static Object getDao(String beanName) {
            // 先判断缓存中是否存在bean
            if(!cache.containsKey(beanName)){
                // 加锁,完成在多线程下的单例
                synchronized (BeanFactory.class){
                    if(!cache.containsKey(beanName)){
                        try {
                            // 获取key对应的value值
                            String value = properties.getProperty(beanName);
                            // 获取到全类名:com.canrui.dao.impl.HelloDaoImpl 后根据反射去创建对象
                            Class clazz = Class.forName(value);
                            Object object = clazz.newInstance();
                            cache.put(beanName, object);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        } catch (InstantiationException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            return cache.get(beanName);
        }
    }
    
    • 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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4DZbTXu-1662261712459)(/Users/wangxinnan/Library/Application Support/typora-user-images/image-20220903131746296.png)]

    1private HelloDao helloDao = new HelloDaoImpl();
    
    2private HelloDao helloDao =HelloDaoBeanFactory.getDao("helloDao");
    
    • 1
    • 2
    • 3

    1、强依赖/紧耦合,编译之后无法修改,没有扩展性。

    2、弱依赖/松耦合,编译之后仍然可以修改,让程序具有更好的扩展性。

    自己放弃了创建对象的权限,将创建对象的权限交给了 BeanFactory,这种将控制权交给别人的思想,就是控制反转 IoC。

    这里有个问题,为什么要双重校验锁?内层的if如果没了,打印台输出不来这些信息?这个代码是不是有些问题?

    解答:https://blog.csdn.net/weixin_44214900/article/details/116068342

    2.2.1 为什么双重检验
    public static Singleton getInstance() {
        if (instance == null) {//线程1,2同时到达,均通过(instance == null)判断。
                                // 线程1进入下面的同步块,线程2被阻塞
            synchronized (Singleton.class) {
                if (instance == null) {//线程1执行发现instance为null,初始化实例后,释放锁。
                    // 线程2进入同步块,此次instance已经被初始化。无法通过if条件,避免多次重复初始化。
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    执行双重检测是因为,如果多个线程通过了第一次检测,此时因为synchronized,其中一个线程会首先通过了第二次检测并实例化了对象,剩余的线程不会再重复实例化对象。这样,除了初始化的时候会加锁,后续的调用都是直接返回,解决了多余的性能消耗。

    小结:

    • 外层判断:完成实例化后,之后的线程就不需要再执行synchronized等待,提高效率。
    • 内层判断:防止多次实例化。
    2.2.2 为什么加双锁
    if (instance == null) {
      instance = new Singleton();//erro
    }
    
    • 1
    • 2
    • 3

    如果不使用volatile关键字,隐患来自于上述代码中注释了 erro 的一行,这行代码大致有以下三个步骤:

    1. 在堆中开辟对象所需空间,分配地址
    2. 根据类加载的初始化顺序进行初始化
    3. 将内存地址返回给栈中的引用变量

    由于 Java 内存模型允许“无序写入”,有些编译器因为性能原因,可能会把上述步骤中的 2 和 3 进行重排序,顺序就成了

    1. 在堆中开辟对象所需空间,分配地址
    2. 将内存地址返回给栈中的引用变量(此时变量已不在为null,但是变量却并没有初始化完成)
    3. 根据类加载的初始化顺序进行初始化

    在这里插入图片描述

    通过对比发现,关键变化在于有volatile修饰的变量,赋值后(前面mov%eax,0x150(%esi)这句便是赋值操作)多执行了一个“lock addl$0x0,(%esp)”操作,这个操作的作用相当于一个内存屏障(Memory Barrier或Memory Fence,指重排序时不能把后面的指令重排序到内存屏障之前的位置)。

    小结:
      这里简单解释一下,在putstatic操作之前设置内存屏障,保证之前的3步骤无法重排在2步骤之前。

    3. Spring IoC 的注解使用

    首先导入spring依赖:

    
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-contextartifactId>
                <version>5.3.22version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    XML 和注解,XML 已经被淘汰了,目前主流的是基于注解的方式,Spring Boot 就是基于注解的方式。

    3.1 热身

    新建spring包,下面有一个entity包---->Account类

    @Data
    //如果要加注释,一定要两个都加上,只加第一个会报错的。
    //@AllArgsConstructor
    //@NoArgsConstructor
    @Component
    public class Account {
        @Value("1")
        private Integer id;
        @Value("canrui")
        private String name;
        @Value("18")
        private Integer age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    还有个test包—>Test 记得是类名首字母小写,不过当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致

    public class Test {
        public static void main(String[] args) {
            //加载IoC容器
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.canrui.spring.entity");
            System.out.println(applicationContext.getBean("account"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    在看仔细些

    public class Test {
        public static void main(String[] args) {
            //加载IoC容器
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.canrui.spring.entity");
            String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
            System.out.println(applicationContext.getBeanDefinitionCount());
    
            //看看都有啥
            for (String beanDefinitionName:beanDefinitionNames) {
                //名字
                System.out.println(beanDefinitionName);
                //通过名字把所有bean取出来
                System.out.println(applicationContext.getBean(beanDefinitionName));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    四个默认的

    在这里插入图片描述

    3.2 Autowired使用

    我们在定义一个实体类:

    @Data
    @Component
    public class Order {
        @Value("XXX15")
        private String orderId;
        @Value("1000.0")
        private Float Price;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们现在希望将Order对象赋给Account

    @Data
    @Component
    public class Account {
        @Value("1")
        private Integer id;
        @Value("canrui")
        private String name;
        @Value("18")
        private Integer age;
        @Autowired
        private Order order;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    此刻我们使用@Autowired,她就会去找

    在这里插入图片描述

    运行后的结果

    在这里插入图片描述

    小知识:@Autowired是根据类型来找—>Order(上图的倒数第一行)

    ​ @@Qualifier(“order”),很显然,是根据名字来找的。即,通过属性的名字和IOC里面bean的名字来进行查找 (上图的倒数第二行)

    4. IoC 基于注解的执行原理

    在这里插入图片描述

    获取原材料(同之前的写的BeanFactory),然后将他们封装到上图中的BeanDefinitions

    在这里插入图片描述

    5. 手写IOC–注解版

    手写代码的思路:

    1、自定义一个 MyAnnotationConfigApplicationContext,构造器中传入要扫描的包。

    2、获取这个包下的所有类。

    3、遍历这些类,找出添加了 @Component 注解的类,获取它的 Class 和对应的 beanName(就是那个会把一个单词的首字母变成小写的),封装成一个 BeanDefinition,存入集合 Set,这个机会就是 IoC 自动装载的原材料。(set可以去重)

    4、遍历 Set 集合,通过反射机制创建对象,同时检测属性有没有添加 @Value 注解,如果有还需要给属性赋值,再将这些动态创建的对象以 k-v 的形式存入缓存区。

    5、提供 getBean 等方法,通过 beanName 取出对应的 bean 即可。

    先去掉maven的spring,然后创建myspring包,在里面开始写我们的代码吧!

    代码实现:

    先是工具类(不用自己写,功能是扫描包下文件)

    package com.canrui.myspring;
    
    import java.io.File;
    import java.io.FileFilter;
    import java.io.IOException;
    import java.net.JarURLConnection;
    import java.net.URL;
    import java.net.URLDecoder;
    import java.util.Enumeration;
    import java.util.LinkedHashSet;
    import java.util.Set;
    import java.util.jar.JarEntry;
    import java.util.jar.JarFile;
    
    public class MyTools {
    
        public static Set<Class<?>> getClasses(String pack) {
    
            // 第一个class类的集合
            Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
            // 是否循环迭代
            boolean recursive = true;
            // 获取包的名字 并进行替换
            String packageName = pack;
            String packageDirName = packageName.replace('.', '/');
            // 定义一个枚举的集合 并进行循环来处理这个目录下的things
            Enumeration<URL> dirs;
            try {
                dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
                // 循环迭代下去
                while (dirs.hasMoreElements()) {
                    // 获取下一个元素
                    URL url = dirs.nextElement();
                    // 得到协议的名称
                    String protocol = url.getProtocol();
                    // 如果是以文件的形式保存在服务器上
                    if ("file".equals(protocol)) {
                        // 获取包的物理路径
                        String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                        // 以文件的方式扫描整个包下的文件 并添加到集合中
                        findClassesInPackageByFile(packageName, filePath, recursive, classes);
                    } else if ("jar".equals(protocol)) {
                        // 如果是jar包文件
                        // 定义一个JarFile
                        System.out.println("jar类型的扫描");
                        JarFile jar;
                        try {
                            // 获取jar
                            jar = ((JarURLConnection) url.openConnection()).getJarFile();
                            // 从此jar包 得到一个枚举类
                            Enumeration<JarEntry> entries = jar.entries();
                            findClassesInPackageByJar(packageName, entries, packageDirName, recursive, classes);
                        } catch (IOException e) {
                            // log.error("在扫描用户定义视图时从jar包获取文件出错");
                            e.printStackTrace();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return classes;
        }
    
        private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, final boolean recursive, Set<Class<?>> classes) {
            // 同样的进行循环迭代
            while (entries.hasMoreElements()) {
                // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                JarEntry entry = entries.nextElement();
                String name = entry.getName();
                // 如果是以/开头的
                if (name.charAt(0) == '/') {
                    // 获取后面的字符串
                    name = name.substring(1);
                }
                // 如果前半部分和定义的包名相同
                if (name.startsWith(packageDirName)) {
                    int idx = name.lastIndexOf('/');
                    // 如果以"/"结尾 是一个包
                    if (idx != -1) {
                        // 获取包名 把"/"替换成"."
                        packageName = name.substring(0, idx).replace('/', '.');
                    }
                    // 如果可以迭代下去 并且是一个包
                    if ((idx != -1) || recursive) {
                        // 如果是一个.class文件 而且不是目录
                        if (name.endsWith(".class") && !entry.isDirectory()) {
                            // 去掉后面的".class" 获取真正的类名
                            String className = name.substring(packageName.length() + 1, name.length() - 6);
                            try {
                                // 添加到classes
                                classes.add(Class.forName(packageName + '.' + className));
                            } catch (ClassNotFoundException e) {
                                // .error("添加用户自定义视图类错误 找不到此类的.class文件");
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    
        private static void findClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) {
            // 获取此包的目录 建立一个File
            File dir = new File(packagePath);
            // 如果不存在或者 也不是目录就直接返回
            if (!dir.exists() || !dir.isDirectory()) {
                // log.warn("用户定义包名 " + packageName + " 下没有任何文件");
                return;
            }
            // 如果存在 就获取包下的所有文件 包括目录
            File[] dirfiles = dir.listFiles(new FileFilter() {
                // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
                @Override
                public boolean accept(File file) {
                    return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
                }
            });
            // 循环所有文件
            for (File file : dirfiles) {
                // 如果是目录 则继续扫描
                if (file.isDirectory()) {
                    findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
                } else {
                    // 如果是java类文件 去掉后面的.class 只留下类名
                    String className = file.getName().substring(0, file.getName().length() - 6);
                    try {
                        // 添加到集合中去
                        // classes.add(Class.forName(packageName + '.' +
                        // className));
                        // 经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
                        classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
                    } catch (ClassNotFoundException e) {
                        // log.error("添加用户自定义视图类错误 找不到此类的.class文件");
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }
    
    • 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
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141

    四个自定义注解

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Qualifier {
        String value();
    }
    
    // 给成员变量加用Field
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Value {
        // 用Value必须给值,所以不用加default
        String value();
    }
    
    // component一般都是给类加的,所以括号里写这个
    @Target(ElementType.TYPE)
    // 运行时机--我们是在运行时使用
    @Retention(RetentionPolicy.RUNTIME)
    // 定义注解
    public @interface Component {
        // 这行用于Component()括号内要传String值,default不写,括号还为空的话就报错
        String value() default "";
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Autowired {
    }
    
    • 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

    两个实体类

    @Data
    //"favorite"
    @Component()
    public class Charater {
        @Value("1")
        private String peopleId;
        @Value("2.3")
        private Float presence;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Component
    public class Animation {
        @Value("10")
        private Integer id;
        @Value("chainsaw_man")
        private String name;
        @Value("100")
        private Integer score;
    
        //自动装载
        @Autowired
        //@Qualifier("favorite")
        private Charater charater;
    }
    
    • 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

    至此准备工作完成。先来定义BeanDefinition,里面之前的如图所示,包含两个变量

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class BeanDefinition {
        private String beanName;
        private Class beanClass;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    最后就是我们的IOC实现,通过反射的各种方法来完成。

    package com.canrui.myspring;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.*;
    
    public class AnnotationConfigApplicationContext {
    
        private Map<String,Object> ioc = new HashMap<>();
    
        // 自定义一个 MyAnnotationConfigApplicationContext,构造器中传入要扫描的包。
        public AnnotationConfigApplicationContext(String pack) {
            // 第一步、获取这个包下的所有类。(原材料)
            Set<BeanDefinition> beanDefinitions = findBeanDefinition(pack);
    
            // 第二步、根据原材料创建bean
            createObject(beanDefinitions);
    
            // 第三步、自动装载
            autowireObject(beanDefinitions);
        }
    
        //自动装载实现--和根据原材料创造bean差不多
        public void autowireObject(Set<BeanDefinition> beanDefinitions){
            Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
            while (iterator.hasNext()) {
                BeanDefinition beanDefinition = iterator.next();
                Class clazz = beanDefinition.getBeanClass();
                Field[] declaredFields = clazz.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    Autowired annotation = declaredField.getAnnotation(Autowired.class);
                    if(annotation!=null){
                        Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
                        if(qualifier!=null){
                            //byName
                            try {
                                String beanName = qualifier.value();
                                Object bean = getBean(beanName);
                                String fieldName = declaredField.getName();
                                String methodName = "set"+fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1);
                                Method method = clazz.getMethod(methodName, declaredField.getType());
                                Object object = getBean(beanDefinition.getBeanName());
                                method.invoke(object, bean);
                            } catch (NoSuchMethodException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }else{
                            //bytype
                            try {
                                // 在前面通过declaredFields获取到Animation所有属性,然后到这里的就是有Autowired注解的
                                // 获取setter参数
                                String fieldName = declaredField.getName(); //charater
                                Object bean = getBean(fieldName); // Charater(peopleId=1, presence=2.3)
                                // 获取方法对象
                                String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                                Method method = clazz.getMethod(methodName, declaredField.getType());
                                // 获取对象--Animation
                                Object object = getBean(beanDefinition.getBeanName());
                                method.invoke(object, bean);
                            } catch (NoSuchMethodException e) {
                                e.printStackTrace();
                            } catch (SecurityException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (IllegalArgumentException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    
        // 这里在提供一个getBean方法
        public Object getBean(String beanName){
            return ioc.get(beanName);
        }
    
        public void createObject(Set<BeanDefinition> beanDefinitions){
            // 1、遍历得到每个对象
            Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
            while (iterator.hasNext()){
                BeanDefinition beanDefinition = iterator.next();
                Class clazz = beanDefinition.getBeanClass();
                String beanName = beanDefinition.getBeanName();
                try {
                    // 2、创建对象
                    Object object = clazz.newInstance();
                    // 3、完成属性的赋值
                    Field[] declaredFields = clazz.getDeclaredFields();
                    for (Field declaredField : declaredFields) {
                        Value valueAnnotation = declaredField.getAnnotation(Value.class);
                        if (valueAnnotation != null){
                            // 这里拿到value值,都是String
                            String value = valueAnnotation.value();
                            // 通过set方法完成赋值,这里还是通过反射调用实体类的set方法,不过这里需要拼接一下
                            String filename = declaredField.getName();
                            String methodName = "set"+filename.substring(0,1).toUpperCase()+filename.substring(1);
                            // 根据方法名和参数列表获取到Method方法对象
                            Method method = clazz.getMethod(methodName, declaredField.getType());
                            // 完成数据类型转换,比如从String->Integer
                            Object val = null;
                            switch (declaredField.getType().getSimpleName()){
                                case "Integer":
                                    val = Integer.parseInt(value);
                                    break;
                                case "String":
                                    val = value;
                                    break;
                                case "Float":
                                    val = Float.parseFloat(value);
                                    break;
                            }
                            // 通过方法对象来访问方法
                            method.invoke(object,val);
                        }
                    }
                    // 4、把创建后的对象和beanName放到Map(缓存)里
                    ioc.put(beanName,object);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public Set<BeanDefinition> findBeanDefinition(String pack){
    
            // 1、获取包下所有类
            Set<Class<?>> classes = MyTools.getClasses(pack);
            Iterator<Class<?>> iterator = classes.iterator();
            Set<BeanDefinition> beanDefinitions = new HashSet<>();
    
            // 2、遍历这些类,找到添加了注解的类
            while (iterator.hasNext()){
                // 2.1 获取class对象
                Class<?> clazz = iterator.next();
                // 返回一个注解对象
                Component componentAnnotation = clazz.getAnnotation(Component.class);
                if (componentAnnotation != null){
                    // 2.2 获取Component注解的值---beanName
                    String beanName = componentAnnotation.value();
                    // 如果Component括号里没有写
                    if ("".equals(beanName)){
                        // 2.2.1 我们就要类名首字母小写
    
                        // 我们需要对类名进行处理,因为现在拿到的是包含包名的类名
                        // com.canrui.myspring.entity.Animation
                        // com.canrui.myspring.entity.Charater
                        String className = clazz.getName().replaceAll(clazz.getPackageName() + ".", "");
                        // 拿到类名首字母变小写
                        beanName = className.substring(0,1).toLowerCase()+className.substring(1);
                    }
                    // 3、将这些类封装成BeanDefinition,装载到集合中
                    BeanDefinition beanDefinition = new BeanDefinition(beanName,clazz);
                    beanDefinitions.add(beanDefinition);
                }
            }
            return beanDefinitions;
        }
    }
    
    • 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
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174

    参考:b站–楠哥教你学java

    自制IOC-xml版本

    耦合/依赖

    依赖指的是某某某离不开某某某
    在软件系统中,层与层之间是存在依赖的。我们也称之为耦合。比如我删掉service层,controller层直接就报错。同样的service也依赖于DAO层。
    我们系统架构或者是设计的一个原则是: 高内聚低耦合。层内部的组成应该是高度聚合的,而层与层之间的关系应该是低耦合的,最理想的情况0耦合(就是没有耦合)

    IOC - 控制反转 / DI - 依赖注入

    如何解决呢?
    首先把依赖的地方变为null

      private FruitService fruitService = null ;
      private FruitDAO fruitDAO = null ;
    
    • 1
    • 2

    此处是和类的多态一样,父类的引用指向子类的对象;这里应该是接口的引用指向了实现接口的类的对象

    然后开始考虑如何改正空指针异常,配置三个bean,对应三个组件。之后,我们需要描述组建和组件之间的依赖关系。
    系统启动时,三个组件就会准备在容器里,谁要就自动给他。

    
    
    <beans>
        <bean id="fruitDAO" class="com.example.fruit.dao.impl.FruitDAOImpl"/>
        <bean id="fruitService" class="com.example.fruit.service.impl.FruitServiceImpl">
    
            <property name="fruitDAO" ref="fruitDAO"/>
        bean>
        
        <bean id="fruit" class="com.example.fruit.controllers.FruitController">
            <property name="fruitService" ref="fruitService"/>
        bean>
    beans>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    新建一个包io,里面新建一个接口。此方法目的是根据id就可以获取到对应的bean对象(id就是上面xml的id)。再写实现类ClassPathXmlApplicationContext。

    public interface BeanFactory {
        Object getBean(String id);
    }
    
    • 1
    • 2
    • 3

    在这部分我们把 beanMap和init方法(负责解析xml的代码) 都放到类ClassPathXmlApplicationContext。
    同时,直接用beanFactory接口得到我们要的对应类。

    public class ClassPathXmlApplicationContext implements BeanFactory {
    
        private Map<String,Object> beanMap = new HashMap<>();
    
        public ClassPathXmlApplicationContext(){
            try {
                InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
                //1.创建DocumentBuilderFactory
                DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
                //2.创建DocumentBuilder对象
                DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;
                //3.创建Document对象
                Document document = documentBuilder.parse(inputStream);
    
                //4.获取所有的bean节点
                NodeList beanNodeList = document.getElementsByTagName("bean");
                for(int i = 0 ; i<beanNodeList.getLength() ; i++){
                    Node beanNode = beanNodeList.item(i);
                    if(beanNode.getNodeType() == Node.ELEMENT_NODE){
                        Element beanElement = (Element)beanNode ;
                        String beanId =  beanElement.getAttribute("id");
                        String className = beanElement.getAttribute("class");
                        //返回指定类名的class对象
                        Class beanClass = Class.forName(className);
                        //创建bean实例
                        Object beanObj = beanClass.newInstance() ;
                        //将bean实例对象保存到map容器中
                        beanMap.put(beanId , beanObj) ;
                        //到目前为止,此处需要注意的是,bean和bean之间的依赖关系还没有设置
                    }
                }
                //5.组装bean之间的依赖关系
                for(int i = 0 ; i<beanNodeList.getLength() ; i++){
                    Node beanNode = beanNodeList.item(i);
                    if(beanNode.getNodeType() == Node.ELEMENT_NODE) {
                        Element beanElement = (Element) beanNode;
                        String beanId = beanElement.getAttribute("id");
                        //拿子节点,xml子节点包括空白
                        NodeList beanChildNodeList = beanElement.getChildNodes();
                        for (int j = 0; j < beanChildNodeList.getLength() ; j++) {
                            Node beanChildNode = beanChildNodeList.item(j);
                            //不关心空白,保证是元素节点,并且节点名字叫property
                            if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
                                Element propertyElement = (Element) beanChildNode;
                                String propertyName = propertyElement.getAttribute("name");
                                String propertyRef = propertyElement.getAttribute("ref");
                                //1) 找到propertyRef对应的实例
                                Object refObj = beanMap.get(propertyRef);
                                //2) 将refObj设置到当前bean对应的实例的property属性上去
                                Object beanObj = beanMap.get(beanId);
                                Class beanClazz = beanObj.getClass();
                                //根据属性名获取field对象
                                Field propertyField = beanClazz.getDeclaredField(propertyName);
                                propertyField.setAccessible(true);
                                //设置beanObj对象的值为refObj
                                propertyField.set(beanObj,refObj);
                            }
                        }
                    }
                }
            } catch (ParserConfigurationException e) {
                e.printStackTrace();
            } catch (SAXException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    
    
        @Override
        public Object getBean(String id) {
            return beanMap.get(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
    • 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
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
  • 相关阅读:
    什么是Redis脑裂,如何解决呢
    剖析Framework面试—>>>冲击Android高级职位
    【unity笔记】八、Unity人物动画介绍
    C#:计算机视觉与OpenCV 的目标
    2023高教社杯 国赛数学建模B题思路 - 多波束测线问题
    ------构造类型数据—结构体---- + ----函数-----
    【Vue】ref,reactive 响应式引用的用法和原理(1)
    UML统一建模语言
    C#函数基础
    Java面向对象高级
  • 原文地址:https://blog.csdn.net/jsdoulaoula/article/details/126687214