Напишите структуру Spring вручную, чтобы углубить свое понимание рабочего механизма Spring!

Java Spring

В нашей повседневной работе мы часто используем фреймворки с открытым исходным кодом, такие как Spring, Spring Boot, Spring Cloud, Struts, Mybatis и Hibernate.С появлением этих фреймворков обычная рабочая нагрузка разработки становится все проще и проще.Мы используемSpring BootВы можете создать новый веб-проект за считанные минуты.

Я помню, как использовал его, когда впервые начал работатьServletПисатьWebпроект, пиши сампул соединений с базой данных, с роднымJDBCРаботайте с базой данных, не будем расходиться. Возвращаясь к теме этой статьи, сегодня я помогу вам понять рабочий механизм Spring, написав инфраструктуру Spring.Код, включенный в эту статью, используется только для того, чтобы помочь вам понять Spring, и не будет использоваться в Интернете.

Структура проекта

file

Реализация части фреймворка

  1. Чтобы различать код фреймворковой части и код бизнес-части, мы разделяем эти две части на разные пакеты.com.mars.demoиcom.mars.framework, чтобы можно было отсканировать только бизнес-код.
  2. Вот мой собственный написанный от руки фреймворк Spring, так что он не будет представлять какие-либо пакеты, связанные с проектом Spring.
  3. Поскольку это веб-проект, все, что нам нужно представитьservlet-apiпакет, только для использования компилятором, все конфигурацииscopeзаprovided.

Создать новый сервлет

Сначала создайте новый класс реализации HttpServlet.MarsDispatcherServlet, используемый для получения запросов.

public class MarsDispatcherServlet extends HttpServlet {


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6. 处理请求
    }

    @Override
    public void init(ServletConfig config) throws ServletException {

    }

настроить web.xml

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Spring Mvc Education</display-name>

    <servlet>
        <servlet-name>marsmvc</servlet-name>
        <servlet-class>com.mars.framework.servlet.MarsDispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>application.properties</param-value>
        </init-param>

        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>marsmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

  1. Сначала настраивается сервлет, имя — marsmvc, а полный путь к классу —com.mars.framework.servlet.MarsDispatcherServlet.
  2. Задаются имена и значения параметров инициализации (Значение здесь — файл конфигурации для всего проекта.).
  3. настроитьload-on-startup, указывает, должен ли контейнер загружать этот сервлет при запуске (создавать экземпляр и вызывать его метод init()).
  4. настроитьservlet-mapping, перенаправляет все запросы этому сервлету для обработки.

Настроить application.properties

scanPackage=com.mars.demo

Это понять несложно, настраивается только один пункт, а значит пакет нужно сканировать, и тогда мы получим это значение для загрузки контейнера.

Определить наши часто используемые аннотации

  1. MarsAutowired
  2. MarsController
  3. MarsRequestMapping
  4. MarsRequestParam
  5. MarsService

Здесь перечислены только два. Остальные похожи. Те, кому нужен исходный код, могут пойти в мой репозиторий кода для форка.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MarsController {
    String value() default "";
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MarsRequestMapping {
    String value() default "";
}

Расширение функций сервлета

Сначала перечислите, что должен делать фреймворк при инициализации.

  1. загрузить файл конфигурации
  2. сканировать все связанные классы
  3. Инициализируйте все связанные классы и сохраните их в контейнере IOC.
  4. Выполнить внедрение зависимостей (назначить поля, аннотированные @Autowired)
  5. Создайте HandlerMapping, чтобы связать URL и метод

Далее выполняем вышеописанную операцию шаг за шагом

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("===================");
        //1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        
        //2.扫描所有相关联的类
        doScanner(contextConfig.getProperty("scanPackage"));
        
        //3.初始化所有相关联的类,并且将其保存在IOC容器里面
        doInstance();
        
        //4.执行依赖注入(把加了@Autowired注解的字段赋值)
        doAutowired();

        //Spring 和核心功能已经完成 IOC、DI
        
        //5.构造HandlerMapping,将URL和Method进行关联
        initHandlerMapping();

        System.out.println("Mars MVC framework initialized");

    }

загрузить файл конфигурации

    private Properties contextConfig = new Properties();

    private void doLoadConfig(String location) {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);

        try {
            contextConfig.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

сканировать все связанные классы

    private void doScanner(String basePackage) {
        //获取要扫描包的url
        URL url =  this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));

        File dir = new File(url.getFile());
        //遍历包下面所有文件
        for(File file: dir.listFiles()) {
            if(file.isDirectory()){
                //递归扫描
                doScanner(basePackage + "." + file.getName());
            } else {
                String className = basePackage + "." + file.getName().replace(".class", "");

                classNames.add(className);

                System.out.println(className);
            }
        }

    }

Инициализируйте все связанные классы и сохраните их в контейнере IOC.

private void doInstance() {

        if(classNames.isEmpty()) return;

        for(String className: classNames) {

            try {
                Class<?> clazz = Class.forName(className);


                if(clazz.isAnnotationPresent(MarsController.class)) {

                    Object instance = clazz.newInstance();
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);

                } else if (clazz.isAnnotationPresent(MarsService.class)) {

                    MarsService service = clazz.getAnnotation(MarsService.class);

                    //2.优先使用自定义命名
                    String beanName = service.value();

                    if("".equals(beanName.trim())) {
                        //1.默认使用类名首字母小写
                        beanName = lowerFirstCase(clazz.getSimpleName());
                    }

                    Object instance = clazz.newInstance();

                    ioc.put(beanName, instance);

                    //3.自动类型匹配(例如:将实现类赋值给接口)

                    Class<?> [] interfaces = clazz.getInterfaces();

                    for(Class<?> inter: interfaces) {
                        ioc.put(inter.getName(), instance);
                    }

                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    //利用ASCII码的差值
    private String lowerFirstCase(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

Выполнить внедрение зависимостей (назначить поля, аннотированные @Autowired)

private void doAutowired() {

        if(ioc.isEmpty()) return;

        for(Map.Entry<String, Object> entry: ioc.entrySet()) {
            //注入的意思就是把所有的IOC容器中加了@Autowired注解的字段赋值
            //包含私有字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for(Field field : fields) {

                //判断是否加了@Autowired注解
                if(!field.isAnnotationPresent(MarsAutowired.class)) continue;

                MarsAutowired autowired = field.getAnnotation(MarsAutowired.class);

                String beanName = autowired.value();

                if("".equals(beanName)) {
                    beanName = field.getType().getName();
                }

                //如果这个字段是私有字段的话,那么要强制访问
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

Создайте HandlerMapping, чтобы связать URL и метод

private void initHandlerMapping() {
        if(ioc.isEmpty()) return;

        for(Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();

            if(!clazz.isAnnotationPresent(MarsController.class)) continue;

            String baseUrl = "";

            if(clazz.isAnnotationPresent(MarsRequestMapping.class)) {
                MarsRequestMapping requestMapping = clazz.getAnnotation(MarsRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            Method[] methods = clazz.getMethods();

            for(Method method : methods) {

                if(!method.isAnnotationPresent(MarsRequestMapping.class)) continue;

                MarsRequestMapping requestMapping = method.getAnnotation(MarsRequestMapping.class);

                String regex = requestMapping.value();

                regex = (baseUrl + regex).replaceAll("/+", "/");

                Pattern pattern = Pattern.compile(regex);
                handlerMapping.add(new Handler(entry.getValue(), method, pattern));

                System.out.println("Mapping: " + regex + "," + method.getName());
            }
        }

    }

Напишите бизнес-код

Создать новый контроллер

@MarsController
@MarsRequestMapping("/demo")
public class DemoApi {

    @MarsAutowired
    private DemoService demoService;

    @MarsRequestMapping("/query")
    public void query(HttpServletRequest req,
                      HttpServletResponse resp,
                      @MarsRequestParam("name") String name) {
        System.out.println("name: " + name);
        String result = demoService.get(name);

        try{
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @MarsRequestMapping("/add")
    public void add(HttpServletRequest req,
                    HttpServletResponse resp,
                    @MarsRequestParam("a") Integer a,
                    @MarsRequestParam("b") Integer b) {
        try {
            resp.getWriter().write(String.format("%d+%d=%d", a, b, (a+b)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Предоставляет два интерфейса: один возвращает вводное содержимое ответа через имя запроса, а другой добавляет запрошенные два целых числа и возвращает их.

Создать службу

public interface DemoService {
    String get(String name);
}

@MarsService
public class DemoServiceImpl implements DemoService {
    public String get(String name) {
        return String.format("My name is %s.", name);
    }
}

Добавить плагин Jetty

Наш проект работает в Jetty, поэтому добавьте соответствующие плагины и конфигурации:

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>7.1.6.v20100715</version>
    <configuration>
        <stopPort>9988</stopPort>
        <stopKey>foo</stopKey>
        <scanIntervalSeconds>5</scanIntervalSeconds>
        <connectors>
            <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
                <port>8080</port>
                <maxIdleTime>60000</maxIdleTime>
            </connector>
        </connectors>
        <webAppConfig>
            <contextPath>/</contextPath>
        </webAppConfig>
    </configuration>
</plugin>

бегать

file

нажмитеjetty:runзапустить проект

Доступ через браузер: http://localhost:8080/demo/query?name=Mars

file

Доступ через браузер: http://localhost:8080/demo/add?a=10&b=20

file

Адрес склада

Добро пожаловать в мой личный блог

Обратите внимание на общедоступный номер:ЯВА 9:30 класс, вот группа отличных программистов, присоединяйтесь к нам, чтобы обсудить технологии и вместе добиться прогресса! Ответьте на «Информация», чтобы получить самую свежую информацию об индустрии 2T!