MCPcopy Index your code
hub / github.com/Patresss/Java-Own-Framework---step-by-step

github.com/Patresss/Java-Own-Framework---step-by-step @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
247 symbols 579 edges 85 files 0 documented · 0%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Java Own Framework - krok po kroku

Celem tego projektu jest pokazanie, w jaki sposób działa dependency injection framework np. Spring.

Repozytorium zademonstruje krok po kroku, w jaki sposób zbudować własny framework. Oczywiście jest to tylko uproszczona forma. Spring jest rozwijany od prawie 20 lat przez setki programistów, więc ledwo zbliżymy się do niego. Jednak repozytorium pokaże koncept takiego frameworka i udowodni, że nie kryje się tam żadna magia.

Filmy:

  • The Magic of Spring • Patryk Piechaczek • Devoxx Poland 2021 - https://www.youtube.com/watch?v=5gttHK04lQ4
  • JDD 2022: Patryk Piechaczek - The Magic of Spring - how does it really work? - https://www.youtube.com/watch?v=p_zTjkos8p0
  • [PL] Bydgoszcz JUG - Meetup #39 - https://www.youtube.com/live/pyA6-tVg2yI?feature=share&t=3719

W repozytorium znajdziecie pakiety z poszczególnymi krokami, a poniżej jest ich omówienie:


Spis kroków


Krok 1 - Budowa aplikacji bez frameworka [kod]

  • Stworzenie szkieletu aplikacji
  • Dao: CompanyDao z implementacją CompanyDaoImpl
  • Service: CompanyService z implementacją CompanyServiceImpl
  • Modelu Company

  • Manualne zarządzanie transakcją

    @Override
    public void createCompany(Company company) {
        try {
            beginTransaction();

            logger.info("SERVICE:   START - create company");
            companyDao.createCompany(company);
            logger.info("SERVICE:   END - create company");

            commitTransaction();
        } catch (Exception e) {
            rollbackTransaction();
        }
    }

Krok 2 (teoria) - JDK Dynamic Proxy (a) vs CGLib (b)

Czym jest Proxy

Proxy jest pośrednikiem, który w łatwy sposób pozwala nam na wykonanie czegoś przed i po konkretnej metodzie:

  class MyClass {

    void method() {
        // ...
    }
}

doSmthBefore();
myClass.method();
doSmthAfter();

Dynamic Proxy

  • Do stworzenia proxy przy pomocy Dynamic Proxy, nie potrzebujemy niczego dodawać do projektu. Proxy- klasa, która odpowiada za tworzenie Proxy znajduje się w JDK.
  • Działa na podstawie interfejsu tzn. jeżeli chcemy stworzyć bean klasy MyServiceImpl, musi ona implementować interfejs MyService.
  • Tworzy klasy $Proxy.

CGLib

  • CGLib jest zewnętrzną biblioteką.
  • Bazuje ona na rozszerzaniu klas, więc proxy nie będzie działało na metodach finalnych.
  • Tworzenie instancji oraz wywoływanie metod przy użyciu CGLib jest szybsze od Dynamic Proxy.
  • Tworzy klasy MyClass$$EnhancerBySpringCGLIB.

Żródło: https://www.baeldung.com/spring-aop-vs-aspectj

Domyślny typ

Domyślnym typem w Springu jest Dynamic Proxy.

Żródło: https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/core.html#aop-introduction-proxies

Jednak gdy stworzymy nową aplikację w Spring Initializr to możemy się zdziwić. Pomimo tego, że dodamy interfejs to w debugu zobaczymy klasę stworzoną przez CGLib X$$EnhancerBySpringCGLIB

Czy dokumentacja kłamie? Nie, domyślna konfiguracja Springa używa Dynamic Proxy, ale już domyśla konfiguracja Spring Boot (2.x) używa CGLib. Powodem tego jest: prevent nasty proxy issues. A czym jest ten "paskudny problem"? Gdy programista spróbuje wstrzyknąć implementację zamiast interfejsu otrzymałby bład:

@Autowired
CompanyServiceImpl companyService;

// vs 

@Autowired
CompanyService companyService;

***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'companyServiceImpl' could not be injected as a 'com.patres.framework.service.CompanyServiceImpl' because it is a JDK dynamic proxy that implements:
    com.patres.framework.service.CompanyService


Action:

Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.

Więcej na ten temat: https://www.programmersought.com/article/87046285018/

Krok 2a - Dynamic Proxy [kod]

W tym projekcie będziemy używać Dynamic Proxy.

Tworzenie InvocationHandler

Niezbędna do tego jest implementacja InvocationHandler. Interfejs posiada 1 metodę:

public Object invoke(Object proxy, Method method, Object[] args)

Przyjmuje on 3 parametry: * proxy — Instancja proxy, w której została wywołana metoda (nie będziemy jej używać, będziemy działali bezpośrednio na obiekcie. W przeciwnym wypadku wywołalibyśmy nieskonczoną rekurencję). * method — Instancja Method, która jest wywoływana przez proxy. * args — Argmenty przekazywane do metody (method).

Przykład implementacji proxy, która jeszcze nic nie robi:

public class ProxyHandler implements InvocationHandler {

    private final Object objectToHandle;

    public ProxyHandler(Object objectToHandle) {
        this.objectToHandle = objectToHandle;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(objectToHandle, args);
    }

}
  • Do konstruktora dodajemy instancje obiektu, którą chcemy obsłużyć — czyli naszą implementację (CompanyServiceImpl).
  • method.invoke(objectToHandle, args) — Robi dokładnie to samo co robiłaby nasza instancja CompanyServiceImpl, gdybyśmy ją wywoływali bezpośrednio. Metoda method jest wywoływana z instancji objectToHandle z argumentami args.

Tworzenie proxy

Aby stworzyć proxy z handlerem, należy użyć klasę Proxy z JDK:

        final CompanyDao companyDao = new CompanyDaoImpl();
        final CompanyService companyServiceProxy = (CompanyService) Proxy.newProxyInstance(
                Step2aApp.class.getClassLoader(),
                new Class[]{CompanyService.class},
                new ProxyHandler(new CompanyServiceImpl(companyDao))
        );

Metoda newProxyInstance przyjmuje 3 parametry: * loader — Class loader, który zdefiniuje proxy * interfaces — Listę interfejsów * h — Invocation handler: klasa, która ma obsłużyć to proxy (w naszym przypadku ProxyHandler)

Obsługa transakcji

Załóżmy, że chcemy stworzyć proxy, aby pomagał nam w obsłudze transakcji. Bez tego musielibyśmy za każdym razem otwierać i zatwierdzać transakcję. Aby uniknąć duplikacji kodu, możemy do tego wykorzystać proxy.

public class ProxyHandler implements InvocationHandler {

    private static final Logger logger = LoggerFactory.getLogger(ProxyHandler.class);
    private final Object objectToHandle;

    public ProxyHandler(Object objectToHandle) {
        this.objectToHandle = objectToHandle;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            beginTransaction();
            final Object invoke = method.invoke(objectToHandle, args);
            commitTransaction();
            return invoke;
        } catch (Exception e) {
            rollbackTransaction();
            throw e;
        }
    }

    private void beginTransaction() {
        logger.debug("BEGIN TRANSACTION");
    }

    private void commitTransaction() {
        logger.debug("COMMIT TRANSACTION");
    }

    private void rollbackTransaction() {
        logger.error("ROLLBACK TRANSACTION");
    }

}

W metodzie invoke: * na początku otwieramy transakcję * wywołujemy metodę, która nas interesuje * zatwierdzamy zmiany * lub je wycofujemy w przypadku błędu


Zwróć uwagę

Na potrzeby tego projektu, nie będziemy implementować prawdziwej transakcji przy użyciu EntityManager — uprościmy to do zwykłego loggera.


Gotowe — proxy, które obsługuje transakcje już działa!

2021-06-13 16:45:39,642 [main] DEBUG         ProxyHandler:32         - BEGIN TRANSACTION
2021-06-13 16:45:39,643 [main] INFO    CompanyServiceImpl:20         - SERVICE:   START - create company
2021-06-13 16:45:39,644 [main] INFO        CompanyDaoImpl:13         - DAO:   START - create company
2021-06-13 16:45:39,645 [main] INFO        CompanyDaoImpl:15         - DAO:   END - create company
2021-06-13 16:45:39,645 [main] INFO    CompanyServiceImpl:22         - SERVICE:   END - create company
2021-06-13 16:45:39,645 [main] DEBUG         ProxyHandler:36         - COMMIT TRANSACTION

Krok 2b - CGLib [kod]

Jak już wspomniałem, w tym projekcie będziemy używać Dynamic Proxy. Jednak przedstawię tworzenie proxy przy pomocy CGLib w ramach ciekawostki.

Tworzenie MethodInterceptor

Podobnie jak w przypadku Dynamic Proxy musimy stworzyć klasę, która będzie zarządzała transakcją. W tym celu stworzymy klasę, którą implementuje MethodInterceptor. Posiada ona jedną metodę:

public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;

z 4 argumentami: * obj - this z rozszerzonej klasy. Inaczej niż w przypadku Dynamic Proxy: tutaj obiekt, którego chcemy obsłużyć dostajemy w parametrze, nie musimy go dodawać do konstruktora. Jest to oczywiste, bo w przypadku Dynamic Proxy działamy na interfejsie, więc nie mamy dostępu do instancji. * method – przechwycona metoda. * args – Argumenty przekazywane do metody (method) * proxy – Służy do wywołania nadrzędnej metody (super)

Przykładowa implementacja:

public class ProxyMethodInterceptor implements MethodInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(ProxyMethodInterceptor.class);

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        try {
            beginTransaction();
            final Object invoke = proxy.invokeSuper(obj, args);
            commitTransaction();
            return invoke;
        } catch (Exception e) {
            rollbackTransaction();
            throw e;
        }
    }

    private void beginTransaction() {
        logger.debug("BEGIN TRANSACTION");
    }

    private void commitTransaction() {
        logger.debug("COMMIT TRANSACTION");
    }

    private void rollbackTransaction() {
        logger.error("ROLLBACK TRANSACTION");
    }

}

Zwróć uwagę

Metodę wywołujemy za pomocą klasy nadrzędnej invokeSuper, w przeciwnym wypadku wpadlibyśmy w nieskończoną rekurencję.


Tworzenie proxy

Tworzenie proxy przy pomocy CGLib odbywa się w kilku krokach:

public class Step2bApp {

    private static final Logger logger = LoggerFactory.getLogger(Step2bApp.class);

    public static void main(String[] args) {
        /* 1 */ final Enhancer enhancer = new Enhancer();
        /* 2 */ enhancer.setSuperclass(CompanyService.class);
        /* 3 */ enhancer.setCallback(new ProxyMethodInterceptor());

        /* 4 */ CompanyService companyService = (CompanyService) enhancer.create(new Class[]{CompanyDao.class}, new Object[]{new CompanyDao()});
        companyService.createCompany(new Company());
    }
}
  1. Inicjalizacja Enhencer.
  2. Definiowanie klasy (w przypadku CGLiB interfejs nie jest wymagany).
  3. Ustawienie Collbacka, który będzie zarządzała proxy (w naszym przypadku, będzie zarządzała transakcjami).
  4. Inicjalizacja proxy: jako argumentu musimy podać tablicę typów i tablicę instancji o określonych typach.

Zwróć uwagę

Aby uruchomić CGLib w Java 16, musimy dodać JVM option - --illegal-access=permit - https://github.com/cglib/cglib/issues/191


Krok 3 - Application Context [kod]

Wiemy już jak działa proxy, więc czas na wstrzykiwanie zależności. Co chcemy osiągnąć? Application Context, który pozwoli nam na pobieranie beanów przy pomocy interfejsów.

public class Step3App {

    public static void main(String[] args) {
        final ApplicationContext applicationContext = new ApplicationContext(Step3App.class);
        final CompanyService companyServiceProxy = applicationContext.getBean(CompanyService.class);

        companyServiceProxy.createCompany(new Company());
    }
}

Adnotacje

Potrzebujemy do tego 2 adnotacji: jedną do definiowania beanów oraz drugą do ich wstrzykiwania: * Autowired - W Springu istnieje kilka sposobów na wstrzyknięcie zależności. Jednak zalecane jest, aby to robić przez konstruktor, dlatego taki też ustawimy target. java @Target(value = ElementType.CONSTRUCTOR) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { }

  • Component - Aby dać znać naszemu frameworkowi, które klasy mają być przez niego zarządzane stworzymy adnotacje @Component. W przypadku Springa jest kilka ta

Extension points exported contracts — how you extend this code

CompanyDao (Interface)
(no doc) [17 implementers]
src/main/java/com/patres/ownframework/step3/application/dao/CompanyDao.java
CompanyDao (Interface)
(no doc) [17 implementers]
src/main/java/com/patres/ownframework/step2a/application/dao/CompanyDao.java
CompanyDao (Interface)
(no doc) [17 implementers]
src/main/java/com/patres/ownframework/step1/application/dao/CompanyDao.java
CompanyDao (Interface)
(no doc) [17 implementers]
src/main/java/com/patres/ownframework/step5/application/dao/CompanyDao.java
CompanyDao (Interface)
(no doc) [17 implementers]
src/main/java/com/patres/ownframework/step6/application/dao/CompanyDao.java

Core symbols most depended-on inside this repo

createCompany
called by 14
src/main/java/com/patres/ownframework/step7/application/dao/CompanyDao.java
generateToken
called by 3
src/main/java/com/patres/ownframework/step5/application/service/CompanyService.java
getBean
called by 3
src/main/java/com/patres/ownframework/step7/framework/ApplicationContext.java
createCompany
called by 3
src/main/java/com/patres/ownframework/step7/application/service/CompanyService.java
generateToken
called by 3
src/main/java/com/patres/ownframework/step7/application/service/CompanyService.java
beginTransaction
called by 2
src/main/java/com/patres/ownframework/step1/application/service/CompanyServiceImpl.java
commitTransaction
called by 2
src/main/java/com/patres/ownframework/step1/application/service/CompanyServiceImpl.java
rollbackTransaction
called by 2
src/main/java/com/patres/ownframework/step1/application/service/CompanyServiceImpl.java

Shape

Method 179
Class 52
Interface 14
Enum 2

Languages

Java100%

Modules by API surface

src/main/java/com/patres/ownframework/step6/framework/ProxyHandler.java11 symbols
src/main/java/com/patres/ownframework/step5/framework/ProxyHandler.java11 symbols
src/main/java/com/patres/ownframework/step4/framework/ProxyHandler.java8 symbols
src/main/java/com/patres/ownframework/step7/framework/ApplicationContext.java7 symbols
src/main/java/com/patres/ownframework/step6/framework/ApplicationContext.java7 symbols
src/main/java/com/patres/ownframework/step5/framework/ApplicationContext.java7 symbols
src/main/java/com/patres/ownframework/step4/framework/ApplicationContext.java7 symbols
src/main/java/com/patres/ownframework/step3/framework/ApplicationContext.java7 symbols
src/main/java/com/patres/ownframework/step1/application/service/CompanyServiceImpl.java7 symbols
src/main/java/com/patres/ownframework/step7/framework/handler/TransactionalHandler.java6 symbols
src/main/java/com/patres/ownframework/step7/application/service/CompanyServiceImpl.java6 symbols
src/main/java/com/patres/ownframework/step6/application/service/CompanyServiceImpl.java6 symbols

For agents

$ claude mcp add Java-Own-Framework---step-by-step \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact