Skip to content

Commit

Permalink
#57 Tomcat server: Add basic embedded tomcat server (#58)
Browse files Browse the repository at this point in the history
* Tomcat server: Add basic embedded tomcat server

* Added ServletInitializer which scan all Controllers and register them in DispatcherServlet, added corresponding annotations

* add logs and javaDocs to all required files

---------

Co-authored-by: mykola.filimonov <[email protected]>
  • Loading branch information
hladchenko and mykolaFilimonov authored Nov 22, 2023
1 parent e1b67a6 commit 958a31a
Show file tree
Hide file tree
Showing 11 changed files with 477 additions and 20 deletions.
39 changes: 27 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Add Maven dependency to your project:
<dependency>
<groupId>io.github.bobocode-breskul</groupId>
<artifactId>bring</artifactId>
<version>1.1</version>
<version>1.3</version>
</dependency>
```

Expand All @@ -18,25 +18,40 @@ The reference [documentation](https://github.com/bobocode-breskul/bring/wiki) in

Here is a quick teaser of a complete Bring application in Java:

Add BringContainer.run("org.example") to your main method, where "org.example" is your package name.

```java
import com.breskul.bring.*;
package org.example;

@RestController
@BringApplication
public class Example {
public class Main {
public static void main(String[] args) {
BringContainer.run("org.example");
}
}
```

@RequestMapping("/")
String home() {
return "Hello World!";
}
Then create a new Controller with following code

public static void main(String[] args) {
BringApplication.run(Example.class, args);
}
```java
package org.example;

import io.github.bobocodebreskul.context.annotations.BringComponent;
import io.github.bobocodebreskul.context.annotations.RestController;
import io.github.bobocodebreskul.context.annotations.Get;

@RestController("/hello")
@BringComponent
public class MyController {

@Get
public String getHello() {
return "Hello, world!";
}
}
```

Now run the application and open http://localhost:8080/hello in your browser.

## Features

### HTTP Server
Expand Down
25 changes: 25 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,18 @@
<assertj.version>3.24.2</assertj.version>
<apache.version>3.13.0</apache.version>
<mockito.version>5.6.0</mockito.version>
<tomcat.version>10.1.16</tomcat.version>
<jakarta.version>6.0.0</jakarta.version>
<jackson.version>2.16.0</jackson.version>
</properties>

<dependencies>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>${jakarta.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
Expand Down Expand Up @@ -121,6 +130,22 @@
<artifactId>commons-lang3</artifactId>
<version>${apache.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>


</dependencies>

Expand Down
15 changes: 15 additions & 0 deletions src/main/java/io/github/bobocodebreskul/MyController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.github.bobocodebreskul;

import io.github.bobocodebreskul.context.annotations.BringComponent;
import io.github.bobocodebreskul.context.annotations.RestController;
import io.github.bobocodebreskul.context.annotations.Get;

@RestController("/hello")
@BringComponent
public class MyController {

@Get
public String getHello() {
return "Hello, world!";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.bobocodebreskul.context.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Indicates that the annotated method serves as a GET request handler within a corresponding Controller.
* This annotation is intended for use in combination with the {@link RestController @RestController} annotation.
*
* <p>Usage:</p>
* <pre>
* {@code
* @RestController("/sample")
* public class SampleController {
*
* @Get
* public YourClass doGet() {
* return new YourClass();
* }
* }}
* </pre>
*
* @see RestController
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Get {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.bobocodebreskul.context.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Indicates that the annotated method serves as a POST request handler within a corresponding Controller.
* This annotation is designed for use in conjunction with the {@link RestController @RestController} annotation.
*
* <p>Usage:</p>
* <pre>
* {@code
* @RestController("/sample")
* public class SampleController {
*
* @Post
* public YourClass doPost() {
* return new YourClass();
* }
* }}
* </pre>
*
* @see RestController
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Post {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.github.bobocodebreskul.context.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Indicates that the annotated class is a RestController, allowing it to be scanned by the ApplicationContext.
* A required request mapping value must be specified. Additionally, HTTP Request Method annotations such as
* {@link Get} or {@link Post} should be added to the methods within the controller. The response from these
* methods will be automatically converted to JSON and sent as the client's response.
*
* <p>Usage:</p>
* <pre>
* {@code
* @RestController("/sample")
* public class SampleController {
*
* @Get
* public YourClass doGet() {
* return new YourClass();
* }
* }}
* </pre>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RestController {

/**
* Represents path to the resource.
*
* @return the filled controller path
*/
String value();
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
import io.github.bobocodebreskul.context.exception.FeatureNotImplementedException;
import io.github.bobocodebreskul.context.exception.InstanceCreationException;
import io.github.bobocodebreskul.context.exception.NoSuchBeanDefinitionException;
import io.github.bobocodebreskul.context.exception.NotFoundDeclaredConstructorException;
import io.github.bobocodebreskul.context.scan.RecursiveClassPathAnnotatedBeanScanner;
import io.github.bobocodebreskul.context.scan.utils.ScanUtilsImpl;
import io.github.bobocodebreskul.server.TomcatServer;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.extern.slf4j.Slf4j;

/**
* Implementation of the {@link ObjectFactory} as Bring beans container. Creates and holds
* all found and registered beans.
* Implementation of the {@link ObjectFactory} as Bring beans container. Creates and holds all found
* and registered beans.
*
* @author Ruslan Hladchenko
* @author Roman Pryshchepa
Expand All @@ -36,18 +38,29 @@ public BringContainer(BeanDefinitionRegistry definitionRegistry) {
}

/**
* Collect all bean definitions by specified scan packages and build container to create and hold all found beans.
* Collect all bean definitions by specified scan packages and build container to create and hold
* all found beans.
*
* @param scanPackages packages where to search beans
* @return created beans container
*/
public static BringContainer run(String... scanPackages) {
BeanDefinitionRegistry definitionRegistry = new SimpleBeanDefinitionRegistry();
AnnotatedBeanDefinitionReader beanDefinitionReader = new AnnotatedBeanDefinitionReader(definitionRegistry);
RecursiveClassPathAnnotatedBeanScanner scanner = new RecursiveClassPathAnnotatedBeanScanner(new ScanUtilsImpl(), beanDefinitionReader);
AnnotatedBeanDefinitionReader beanDefinitionReader = new AnnotatedBeanDefinitionReader(
definitionRegistry);
RecursiveClassPathAnnotatedBeanScanner scanner = new RecursiveClassPathAnnotatedBeanScanner(
new ScanUtilsImpl(), beanDefinitionReader);
scanner.scan(scanPackages);

return new BringContainer(definitionRegistry);
BringContainer container = new BringContainer(definitionRegistry);

definitionRegistry.getBeanDefinitions()
.forEach(beanDefinition -> container.getBean(beanDefinition.getName()));

ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(() -> TomcatServer.run(container));

return container;
}

// TODO: 1. add dependency injection by @Autowired field
Expand All @@ -60,7 +73,9 @@ public Object getBean(String name) {

BeanDefinition beanDefinition = definitionRegistry.getBeanDefinition(name);
if (beanDefinition == null) {
throw new NoSuchBeanDefinitionException("BeanDefinition for bean with name %s is not found! Check configuration and register this bean".formatted(name));
throw new NoSuchBeanDefinitionException(
"BeanDefinition for bean with name %s is not found! Check configuration and register this bean".formatted(
name));
}
Class<?> beanClass = beanDefinition.getBeanClass();
try {
Expand Down Expand Up @@ -90,4 +105,8 @@ public Object getBean(String name) {
public Object getBean(Class<?> clazz) {
throw new UnsupportedOperationException();
}

public List<Object> getAllBeans() {
return storageByName.values().stream().toList();
}
}
Loading

0 comments on commit 958a31a

Please sign in to comment.