前言

传统配置Spring xml配置文件将Bean托管Spring容器。

但由于配置的繁琐性,随着JDK5支持注解,Spring2.x版本后逐渐使用注解方式代替传统XML配置。

现在开发的新项目惯于使用Spring扫包+注解的方式自动装配,托管于Spring容器。

本篇文章基于上述内容,大致描述Spring新版本核心思想,代码只提炼精髓与关键点,且与源码大相径庭,复杂程度远不及源码的N分之一,仅供参考。


目录

具体实现

一、自定义注解

二、编写Controller及Service

三、自定义模拟Spring容器及Url与Method映射关系

四、自动扫包工具类

五、开始注解托管+依赖注入

六、自定义Servlet

七、开始请求,测试结果

http://127.0.0.1:8080/test/query

http://127.0.0.1:8080/test/add?str=含参方式测试 


实现过程:

  1. 【自定义注解】,模仿目前工程中常用注解,自定义名称:@MyController、@MyService、@MyAutowired、@MyRequestMapping、@MyRequestParam
  2. 【定义全局Map模拟Spring容器】,Map<Bean名称,Bean实例>
  3. 【定义全局Map模拟请求url与Controller中方法映射关系】,Map<URL,Method>
  4. 【自动扫包 + 初始化容器】给出扫包路径,根据路径扫描java文件,且筛选具有@MyController、@MyService注解的类注入容器,其中@MyService的类可以起别名,且@MyService实现的接口类也会同时被注入容器(接口与实现类注入Bean的实例均为实现类的实例
  5. 【依赖注入】遍历容器管理的Bean,拿到每个Bean中的Field,筛选具有@MyAutowired的Field,并根据Field的ClassName从容器中获取Bean实例赋值给该Field
  6. 【自定义Servlet】拦截请求,并统一实现请求分发
  7. 【自动处理请求参数】根据@MyRequestParam获取具体参数名称,实现自动请求

具体实现

一、自定义注解

@MyController

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {

    String value() default "";
}

@MyService

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {

    String value() default "";
}

@MyAutowired

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {

    String value() default "";
}

@MyRequestMapping

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {

    String value() default "";
}

@MyRequestParam

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {

    String value() default "";
}

二、编写Controller及Service

  • 分别在各个地方使用自定义注解@MyController、@MyService、@MyAutowired、@MyRequestMapping、@MyRequestParam
@MyController
@MyRequestMapping("/test")
public class TestController {

    @MyAutowired
    private TestService testService;

    @MyRequestMapping("/add")
    public void add(HttpServletRequest request, @MyRequestParam("str") String str) {
        testService.add(str);
    }

    @MyRequestMapping("/query")
    public void query(HttpServletRequest request) {
        testService.query();
    }
}
public interface TestService {

    void add(String str);

    void query();
}
@MyService
public class TestServiceImpl implements TestService {

    @Override
    public void add(String str) {
        System.out.println("add:" + str);
    }

    @Override
    public void query() {
        System.out.println("query方法执行!");
    }
}

三、自定义模拟Spring容器及Url与Method映射关系

public interface CommonField {

    /**
     * 模拟Spring容器,存放Spring初始化Bean
     */
    Map<String, Object> myIocContainer = new HashMap<>();

    /**
     * 模拟SpringMVC请求映射处理器,映射请求url和Method关系
     */
    Map<String, Method> myHandlerMapping = new HashMap<>();
}

四、自动扫包工具类

  • 本地启动扫包与项目打Jar包后扫包方式略有不同
public class FileUtil {

    /**
     * 路径扫包,支持递归 / 非递归
     *
     * @param packagePath 包路径,以"."分隔
     * @param recursion   是否递归   true:是
     * @return
     * @throws Exception
     */
    public static List<Class<?>> scanPackage(String packagePath, boolean recursion) throws Exception {
        List<Class<?>> result = new ArrayList<>();
        String packageRealPath = packagePath.replace(".", "/");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources = classLoader.getResources(packageRealPath);
        while (resources.hasMoreElements()) {
            URL url = resources.nextElement();
            if ("file".equals(url.getProtocol())) {
                // 本地项目启动扫包方式
                result.addAll(scanFile(classLoader, packagePath, recursion));
            } else if ("jar".equals(url.getProtocol())) {
                // 项目打包后扫包方式
                result.addAll(scanJar(classLoader, packagePath, recursion));
            }
        }
        return result;
    }

    private static List<Class<?>> scanFile(ClassLoader classLoader, String packagePath, boolean recursion) throws Exception {
        List<Class<?>> result = new ArrayList<>();
        URI url = Objects.requireNonNull(classLoader.getResource(packagePath.replace(".", "/"))).toURI();
        File file = new File(url);
        if (file.isDirectory()) {
            File[] fileList = file.listFiles();
            if (fileList != null && fileList.length > 0) {
                for (File _file : fileList) {
                    if (!_file.isDirectory()) {
                        String fileName = _file.getName();
                        if (!file.exists() || !fileName.endsWith(".class")) {
                            continue;
                        }
                        String className = packagePath + "." + fileName.replace(".class", "");
                        Class<?> clazz = Class.forName(className);
                        if (isSkipClass(clazz)) {
                            continue;
                        }
                        result.add(clazz);
                    } else if (recursion) {
                        result.addAll(scanFile(classLoader, packagePath + "." + _file.getName(), true));
                    }
                }
            }
        } else {
            throw new Exception("错误的扫包路径!");
        }
        return result;
    }

    private static List<Class<?>> scanJar(ClassLoader classLoader, String packagePath, boolean recursion) throws Exception {
        List<Class<?>> result = new ArrayList<>();
        URL url = classLoader.getResource(packagePath.replace(".", "/"));
        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
        JarFile jarFile = jarURLConnection.getJarFile();
        Enumeration<JarEntry> jarEntrys = jarFile.entries();
        while (jarEntrys.hasMoreElements()) {
            JarEntry jarEntry = jarEntrys.nextElement();
            String jarEntryName = jarEntry.getName();
            /**
             * 读取项目Jar包后遍历所有文件夹及子文件夹,若文件夹路径不是以目标路径开头直接跳过
             * 子文件夹一并读取 因此无需手动递归
              */
            if (!jarEntryName.replaceAll("/", ".").startsWith(packagePath)) {
                continue;
            }
            if (!jarEntry.isDirectory()) {
                if (!jarEntryName.endsWith(".class")) {
                    continue;
                }
                String className = jarEntry.getName().replace("/", ".").replace(".class", "");
                Class<?> clazz = Class.forName(className);
                if (isSkipClass(clazz)) {
                    continue;
                }
                result.add(clazz);
            }
        }
        return result;
    }

    private static boolean isSkipClass(Class<?> clazz) throws Exception {
        // 跳过 注解、接口、枚举、八种基本数据类型
        if (clazz.isAnnotation() || clazz.isInterface() || clazz.isEnum() || clazz.isPrimitive()) {
            return true;
        }
        return false;
    }
}

五、开始注解托管+依赖注入

  • 将带@MyController、@MyService注解的类交给容器托管,并实现依赖注入,
  • 同时确定@RequestMapping注解的请求映射
public class SpringTest {

    public static void test(String packageUrl) throws Exception {
        List<Class<?>> classes = FileUtil.scanPackage(packageUrl, true);
        if (!CollectionUtils.isEmpty(classes)) {
            for (Class<?> clazz : classes) {
                if (clazz.isAnnotationPresent(MyController.class)) {
                    /**
                     * 如果是Controller注解,首先将Controller类托管Spring
                     * 然后获取该Controller的请求路径及方法,交给 请求映射处理器
                     */
                    CommonField.myIocContainer.put(clazz.getName(), clazz.newInstance());
                    String requestBaseUrl = "";
                    if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                        MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
                        requestBaseUrl = requestMapping.value();
                    }
                    Method[] methods = clazz.getMethods();
                    for (Method method : methods) {
                        MyRequestMapping subRequestMapping = method.getAnnotation(MyRequestMapping.class);
                        if (subRequestMapping != null) {
                            String requestUrl = requestBaseUrl + subRequestMapping.value();
                            CommonField.myHandlerMapping.put(requestUrl, method);
                        }
                    }
                } else if (clazz.isAnnotationPresent(MyService.class)) {
                    /**
                     * 如果是Service注解,获取Service别名,
                     *      若其没有别名,则以类名托管Spring容器
                     *      若存在别名,则以别名托管Spring容器
                     *  然后获取Service注解所在类的实现接口,并将接口托管Spring容器
                     */
                    MyService service = clazz.getAnnotation(MyService.class);
                    String serviceName;
                    if ("".equals(service.value())) {
                        serviceName = clazz.getName();
                    } else {
                        serviceName = service.value();
                    }
                    Object newInstance = clazz.newInstance();
                    CommonField.myIocContainer.put(serviceName, newInstance);
                    Class<?>[] serviceInterfaceClasses = clazz.getInterfaces();
                    for (Class<?> serviceInterfaceClazz : serviceInterfaceClasses) {
                        CommonField.myIocContainer.put(serviceInterfaceClazz.getName(), newInstance);
                    }
                }
            }

            /**
             * 遍历容器管理的Bean,拿到每个Bean中的Field
             * 筛选具有@MyAutowired的Field,并根据Field的ClassName从容器中获取Bean实例赋值给该Field
             */
            for (Map.Entry<String, Object> _entry : CommonField.myIocContainer.entrySet()) {
                Object _obj = _entry.getValue();
                if (_obj != null) {
                    Class<?> _clazz = _obj.getClass();
                    Field[] fields = _clazz.getDeclaredFields();
                    for (Field _field : fields) {
                        if (_field.isAnnotationPresent(MyAutowired.class)) {
                            MyAutowired myAutowired = _field.getAnnotation(MyAutowired.class);
                            String beanName = myAutowired.value();
                            if ("".equals(beanName)) {
                                beanName = _field.getType().getName();
                            }
                            _field.setAccessible(true);
                            _field.set(_obj, CommonField.myIocContainer.get(beanName));
                        }
                    }
                }
            }
        }
    }
}

六、自定义Servlet

  • 分发请求,自动传递请求参数
  • 具体自定义方式参考此文章,不再赘述:
@Configuration
public class WebConfig {

    @Bean
    public ServletRegistrationBean registerMyServlet(){
        return new ServletRegistrationBean(new MyDispatcherServlet());
    }

    @Bean
    public ServletListenerRegistrationBean<MyListener> registrationListener(){
        ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
        return servletListenerRegistrationBean;
    }
}
@WebServlet
public class MyDispatcherServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("POST方法进入MyServlet");
        doDispatch(req, resp);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("GET方法进入MyServlet");
        doPost(req, resp);
    }

    /**
     * 请求分发,获取myHandlerMapping中Url请求与Method的映射关系,根据请求锁定具体方法
     * 获取Method中@MyRequestParam入参,若存在,获取具体类型后反射调用
     *
     * @param request
     * @param response
     */
    private void doDispatch(HttpServletRequest request, HttpServletResponse response) {
        String url = request.getRequestURI();
        if (CommonField.myHandlerMapping.containsKey(url)) {
            Method method = CommonField.myHandlerMapping.get(url);
            Map<String, String[]> params = request.getParameterMap();
            Class<?>[] parameterTypes = method.getParameterTypes();
            Object[] invokeParams = new Object[parameterTypes.length];
            if (parameterTypes.length > 0) {
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> _parameterType = parameterTypes[i];
                    if (HttpServletRequest.class.equals(_parameterType)) {
                        invokeParams[i] = request;
                    } else if (HttpServletResponse.class.equals(_parameterType)) {
                        invokeParams[i] = response;
                    } else if (String.class.equals(_parameterType)) {
                        Annotation[][] methodParam = method.getParameterAnnotations();
                        for (int j = 0; j < methodParam.length; j++) {
                            for (Annotation annotation : methodParam[j]) {
                                if (annotation instanceof MyRequestParam) {
                                    String key = ((MyRequestParam) annotation).value();
                                    invokeParams[i] = params.get(key)[0];
                                }
                            }
                        }
                    }
                }
            }
            try {
                method.invoke(CommonField.myIocContainer.get(method.getDeclaringClass().getName()), invokeParams);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

七、开始请求,测试结果

http://127.0.0.1:8080/test/query

spring xml配置扫描 spring扫描包_注解

http://127.0.0.1:8080/test/add?str=含参方式测试 

spring xml配置扫描 spring扫描包_依赖注入_02