仿 Spring 手写 IoC、AOP 框架

一、场景还原

简单的后台服务方案:创建一个 Bean ----> 来个 BeanDao 再来个 ----> BeanService 里面通过 new BeanDao() 来调用持久层方法,最后来个 ----> BeanServlet 通过 new BeanService() 来调用业务层。

相信上面的代码各位大佬都曾写过。那么这些伪代码里都存在哪些问题呢

  1. 耦合严重,所有的对象引用都直指具体的实现类。
  2. 编码繁琐,开发者需编写大量代码去实例化对象并为其属性赋值以完成 Bean 的初始化。
  3. 修改困难,当面对需求修改或某类型的所有方法统一增加日志、鉴权等功能时,会让开发者原地爆炸。

二、解决方案

问题找出来了,那么我们应该怎么去自己动手解决这些问题呢?(那位说用 Spring Framework 的同学,麻烦你请出去一下~!手动狗头)

  1. 创建自己的对象管理容器,让它去帮我们完成对象的创建与赋值,即:IoC
  2. 面向接口编程,降低对象间的耦合性。
  3. 动态代理增强,抽取日志、鉴权等非业务逻辑功能到动态代理方法中,即:AOP
  4. 自定义注解,通过注解标记需要对象管理容器进行处理的类、属性或方法。

三、代码实现

这部分,博主只拿出重点部分出来进行解释说明,具体代码可在文末连接下载。

  1. 首先,解耦合第一步需要进行的是面向接口开发的处理,想 Spring MVCDAOService 层一样,这里不做过多赘述。

  2. 其次,既然要实现 IoC 就一定会有自己的 Bean 容器(单例),用来维护项目中所有的类实例化对象。

BeanContainer

import java.util.HashMap;
import java.util.Map;

/**
 * @author Supreme_Sir
 * @version 1.0
 * @className BeanContainer
 * @description 自定义单例的 Bean 容器
 * @date 2020/10/13 14:05
 **/
public class BeanContainer {
    
    
    /**
     * Bean 容器:装载已完成(可包含实例化不完全的 Bean)实例化的对象
     */
    private Map<String, Object> container = new HashMap<>();
    /**
     * 对象 id 和全限定类名的映射关系记录表
     */
    private Map<String, String> idClassMap = new HashMap<>();

    private static final BeanContainer beanContainer = new BeanContainer();

    private BeanContainer() {
    
    
    }

    /**
     * 获取单例模式下的 BeanContainer 对象
     * @return BeanContainer
     */
    public static BeanContainer getBeanContainer() {
    
    
        return beanContainer;
    }

    /**
     * 向容器中添加 Bean
     * @param id 对象别名
     * @param clazz 全限定类名
     * @param instance 对象实例
     */
    public void put(String id, String clazz, Object instance) {
    
    
        container.put(clazz, instance);
        idClassMap.put(id, clazz);
    }

    /**
     * 向容器中添加 Bean
     * @param clazz 全限定类名
     * @param instance 对象实例
     */
    public void put(String clazz, Object instance) {
    
    
        container.put(clazz, instance);
    }

    /**
     * 获取 Bean,可通过对象别名和全限定类名获取
     * @param key
     * @return
     */
    public Object get(String key) {
    
    
        Object result = container.get(key);
        if (result == null) {
    
    
            result = container.get(idClassMap.get(key));
        }
        return result;
    }
}
  1. 容器有了,那么我们就要创建把类装进方法。该功能的实现主要有一下几个步骤:
    • 获取绝对路径
    • 递归扫描这个路径下的所有 .class 文件。注意:不是 .java 文件,因为真正的代码执行用的不是源码。
    • 加载具有公共空参构造函数的类,但要排除控制层的对象,因为控制层对象的实例化是由 Web 容器管理的。同时,在此处还需登记需要自动装配的类和需要代理增强的类。
    • 为需要自动装配属性的对象赋值。
    • 为需要增强的对象生成代理对象。

BeanScanner

import com.idol.annotation.Autowired;
import com.idol.annotation.Service;
import com.idol.annotation.Transaction;
import com.idol.controller.MyHttpServlet;
import com.idol.proxy.BeanProxyFactory;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

/**
 * @author Supreme_Sir
 * @version 1.0
 * @className BeanScanner
 * @description 用以扫描并实例化指定包下的 Bean
 * @date 2020/10/13 15:26
 **/
public class BeanScanner {
    
    
    private static final String EXT = "class";
    /**
     * 记录需要进行属性填充的类
     */
    private static final Map<String, List<Field>> FIT_FIELD_MAP = new HashMap<>(16);
    /**
     * 记录需要进行 AOP 增强的类
     */
    private static final Map<String, List<Method>> ENHANCER_METHOD_MAP = new HashMap<>(16);

    /**
     * 根据包名获取包的URL
     * @param packageName com.idol
     * @return
     */
    private static String getPackagePath(String packageName) throws UnsupportedEncodingException {
    
    
        String pkgDirName = packageName.replace(".", File.separator);
        URL url = Thread.currentThread().getContextClassLoader().getResource(pkgDirName);
        return url == null ? null : URLDecoder.decode(url.getFile(), "UTF-8");
    }

    /**
     * 遍历指定目录下所有扩展名为class的文件
     */
    private static List<File> getAllClassFile(String strPath, List<File> fileList) {
    
    
        File dir = new File(strPath);
        // 该文件目录下文件全部放入数组
        File[] files = dir.listFiles();
        if (files != null) {
    
    
            for (int i = 0; i < files.length; i++) {
    
    
                String fileName = files[i].getName();
                // 判断是文件还是文件夹
                if (files[i].isDirectory()) {
    
    
                    // 获取文件绝对路径
                    getAllClassFile(files[i].getAbsolutePath(), fileList);
                    // 判断文件名是否以 .class 结尾
                } else if (fileName.endsWith(EXT)) {
    
    
                    fileList.add(files[i]);
                } else {
    
    
                    continue;
                }
            }

        }
        return fileList;
    }

    /**
     * 加载类
     *
     * @param file
     * @param packagePath
     * @param packageName
     * @return
     * @throws ClassNotFoundException
     */
    private static void initObjects(File file, String packagePath, String packageName) throws
            ClassNotFoundException, IllegalAccessException, InstantiationException {
    
    
        // 拿到全限定类名
        String absPath = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - EXT.length() - 1);
        String className = absPath.substring(packagePath.length() - 1).replace(File.separatorChar, '.');
        className = className.startsWith(".") ? packageName + className : packageName + "." + className;

        // 将指定类型的类实例化后放入自定义的 Bean 容器中
        Class<?> clazz = Class.forName(className);
        if (canBeInit(clazz)) {
    
    
            // 登记带有 @Autowired 注解的类属性
            registAutowaredField(clazz);
            // 登记带有 @Transaction 注解的类方法
            registTranscationMethod(clazz);
            Class<?> loadClass = Thread.currentThread().getContextClassLoader().loadClass(className);
            String[] split = className.split("\\.");
            BeanContainer.getBeanContainer().put(split[split.length - 1], className, loadClass.newInstance());
        }
    }

    /**
     * 为 @Autowared 注入装配对象
     *
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static void fitField() throws NoSuchFieldException, IllegalAccessException {
    
    
        for (Map.Entry entry : FIT_FIELD_MAP.entrySet()) {
    
    
            Object bean = BeanContainer.getBeanContainer().get((String) entry.getKey());
            List<Field> fields = (List<Field>) entry.getValue();
            for (Field field : fields) {
    
    
                Field declaredField = bean.getClass().getDeclaredField(field.getName());
                declaredField.setAccessible(true);
                declaredField.set(bean, BeanContainer.getBeanContainer().get(
                        declaredField.getType().toString().split(" ")[1]));
            }
        }
        FIT_FIELD_MAP.clear();
    }

    /**
     * 登记需要自动装配的属性
     *
     * @param clazz
     */
    private static void registAutowaredField(Class<?> clazz) {
    
    
        String name = clazz.getName();
        Field[] fields = clazz.getDeclaredFields();
        List<Field> fieldList = new ArrayList<>();
        for (Field field : fields) {
    
    
            if (field.isAnnotationPresent(Autowired.class)) {
    
    
                fieldList.add(field);
            }
        }
        FIT_FIELD_MAP.put(name, fieldList);
    }

    /**
     * 登记需要进行增强的方法
     *
     * @param clazz
     */
    private static void registTranscationMethod(Class<?> clazz) {
    
    
        if (clazz.isAnnotationPresent(Service.class)) {
    
    
            String name = clazz.getName();
            Method[] methods = clazz.getDeclaredMethods();
            List<Method> methodList = new ArrayList<>();
            for (Method method : methods) {
    
    
                if (method.isAnnotationPresent(Transaction.class)) {
    
    
                    methodList.add(method);
                }
            }
            ENHANCER_METHOD_MAP.put(name, methodList);
        }
    }

    /**
     * 判断当前类是否符合实例化条件
     *
     * @param clazz
     * @return
     */
    private static boolean canBeInit(Class<?> clazz) {
    
    
        boolean result = false;
        if (!clazz.isInterface()) {
    
    
            // 不实例化控制层对象
            if (clazz.getSuperclass() != MyHttpServlet.class) {
    
    
                Constructor<?>[] constructors = clazz.getConstructors();
                // 判断是否有空参的公共构造函数
                if (constructors.length > 0) {
    
    
                    for (Constructor<?> constructor : constructors) {
    
    
                        if (constructor.getParameters().length < 1) {
    
    
                            return true;
                        }
                    }
                }
            }
        }
        return result;
    }

    /**
     * 创建代理对象,开启数据库事务
     */
    private static void createProxy() {
    
    
        BeanProxyFactory proxyFactory = (BeanProxyFactory) BeanContainer.getBeanContainer().get("BeanProxyFactory");
        for (Map.Entry entry : ENHANCER_METHOD_MAP.entrySet()) {
    
    
            String beanContainerKey = (String) entry.getKey();
            Object object = BeanContainer.getBeanContainer().get(beanContainerKey);

            Class<?>[] interfaces = object.getClass().getInterfaces();
            if (interfaces.length > 0) {
    
    
                beanContainerKey = interfaces[0].toString().split(" ")[1];
            }
            object = proxyFactory.createTransactionProxy(object);

            BeanContainer.getBeanContainer().put(beanContainerKey, object);
        }
        ENHANCER_METHOD_MAP.clear();
    }

    /**
     * 扫描指定的包
     *
     * @param packageName
     * @throws UnsupportedEncodingException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws ClassNotFoundException
     * @throws NoSuchFieldException
     */
    public static void scanPackage(String packageName) throws UnsupportedEncodingException,
            IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchFieldException {
    
    
        String packagePath = getPackagePath(packageName);
        // 获取所有 .class 文件
        List<File> classFile = getAllClassFile(packagePath, new ArrayList<File>());
        for (File file : classFile) {
    
    
            // 初始化对象
            initObjects(file, packagePath, packageName);
        }
        // 为对象注入属性
        fitField();
        // 创建数据库事务代理对象
        createProxy();
    }

}
  1. 准备工作做完了,那什么时候对类进行初始化呢?由于本工程是 Web 项目,且服务器使用的 Tomcat。 那么,就从 Tomcat 监听入手。

WebStartListener

import com.idol.factory.BeanScanner;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 * @author Supreme_Sir
 * @version 1.0
 * @className StartListener
 * @description Tomcat 启动监听,以在 Tomcat 启动阶段完成类的自动扫描、加载和装配
 *              注意:本类必须在所有子包的外面
 * @date 2020/10/13 14:26
 **/
@WebListener
public class WebStartListener implements ServletContextListener {
    
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
    
    
        // 获取当前类所在包
        String packageName = WebStartListener.class.getPackage().getName();
        try {
    
    
            BeanScanner.scanPackage(packageName);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    
    
        System.out.println("Tomcat 关闭了~~~");
    }
}
  1. 到这里,就剩下最后一个需要解决的问题了,那就是:控制层的 Bean 是由 Web 容器管理的,而我们又要做怎么才能为其实现服务层属性的自动注入呢?

    我这里给到的处理方式是:插一杠子~!对,就是要在 Tomcat 实例化控制层对象时插一杠子。即:所有的 Servlet 都需要继承 HttpServlet,那么我们就先自定义一个继承自 HttpServlet 的类,然后重写父类中的 init 方法,在 init 方法执行时,完成属性的自动赋值。最后,让所有控制层的类继承自定义的 HttpServlet 即可。

MyHttpServlet

import com.idol.annotation.Autowired;
import com.idol.factory.BeanContainer;

import javax.servlet.http.HttpServlet;
import java.lang.reflect.Field;

/**
 * @author Supreme_Sir
 * @version 1.0
 * @className MyHttpServlet
 * @description 自定义 MyHttpServlet 对象,以在 Servlet 执行前完成控制层具体的对象的属性装配
 * @date 2020/10/14 0:18
 **/
public class MyHttpServlet extends HttpServlet {
    
    
    @Override
    public void init() {
    
    
        Field[] fields = this.getClass().getDeclaredFields();
        for (Field field : fields) {
    
    
            if (field.isAnnotationPresent(Autowired.class)) {
    
    
                field.setAccessible(true);
                try {
    
    
                    field.set(this, BeanContainer.getBeanContainer().get(
                            field.getType().toString().split(" ")[1]));
                } catch (IllegalAccessException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
}

源码

下载源码

---------- 遇良人先成家,遇贵人先立业,未遇良人先自立,未遇贵人先自修。父母帮衬先攒钱,父母不帮顾眼前。 ----------

猜你喜欢

转载自blog.csdn.net/Supreme_Sir/article/details/109100886