В нашей повседневной работе мы часто используем фреймворки с открытым исходным кодом, такие как Spring, Spring Boot, Spring Cloud, Struts, Mybatis и Hibernate.С появлением этих фреймворков обычная рабочая нагрузка разработки становится все проще и проще.Мы используемSpring Boot
Вы можете создать новый веб-проект за считанные минуты.
Я помню, как использовал его, когда впервые начал работатьServlet
ПисатьWeb
проект, пиши сампул соединений с базой данных, с роднымJDBC
Работайте с базой данных, не будем расходиться. Возвращаясь к теме этой статьи, сегодня я помогу вам понять рабочий механизм Spring, написав инфраструктуру Spring.Код, включенный в эту статью, используется только для того, чтобы помочь вам понять Spring, и не будет использоваться в Интернете.
Структура проекта
Реализация части фреймворка
- Чтобы различать код фреймворковой части и код бизнес-части, мы разделяем эти две части на разные пакеты.
com.mars.demo
иcom.mars.framework
, чтобы можно было отсканировать только бизнес-код. - Вот мой собственный написанный от руки фреймворк Spring, так что он не будет представлять какие-либо пакеты, связанные с проектом Spring.
- Поскольку это веб-проект, все, что нам нужно представить
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>
- Сначала настраивается сервлет, имя — marsmvc, а полный путь к классу —
com.mars.framework.servlet.MarsDispatcherServlet
. - Задаются имена и значения параметров инициализации (Значение здесь — файл конфигурации для всего проекта.).
- настроить
load-on-startup
, указывает, должен ли контейнер загружать этот сервлет при запуске (создавать экземпляр и вызывать его метод init()). - настроить
servlet-mapping
, перенаправляет все запросы этому сервлету для обработки.
Настроить application.properties
scanPackage=com.mars.demo
Это понять несложно, настраивается только один пункт, а значит пакет нужно сканировать, и тогда мы получим это значение для загрузки контейнера.
Определить наши часто используемые аннотации
- MarsAutowired
- MarsController
- MarsRequestMapping
- MarsRequestParam
- 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 "";
}
Расширение функций сервлета
Сначала перечислите, что должен делать фреймворк при инициализации.
- загрузить файл конфигурации
- сканировать все связанные классы
- Инициализируйте все связанные классы и сохраните их в контейнере IOC.
- Выполнить внедрение зависимостей (назначить поля, аннотированные @Autowired)
- Создайте 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>
бегать
нажмитеjetty:run
запустить проект
Доступ через браузер: http://localhost:8080/demo/query?name=Mars
Доступ через браузер: http://localhost:8080/demo/add?a=10&b=20
Добро пожаловать в мой личный блог
Обратите внимание на общедоступный номер:ЯВА 9:30 класс, вот группа отличных программистов, присоединяйтесь к нам, чтобы обсудить технологии и вместе добиться прогресса! Ответьте на «Информация», чтобы получить самую свежую информацию об индустрии 2T!