JavaRush /Blog Java /Random-PL /Aplikacja internetowa w Javie
Viacheslav
Poziom 3

Aplikacja internetowa w Javie

Opublikowano w grupie Random-PL
Aplikacja internetowa w Javie – 1

Wstęp

Dawno, dawno temu Java umocniła swoją pozycję dzięki temu, że priorytetowo postawiła na aplikacje webowe. Od samego początku Java miała trudności ze znalezieniem swojej drogi. Najpierw zasugerowałem aplety. Zapewniło to programistom wiele możliwości tworzenia dynamicznej zawartości na statycznych stronach HTML. Jednak aplety nie spełniły oczekiwań z wielu powodów: bezpieczeństwa, kosztów ogólnych i innych. Następnie twórcy języka Java zaproponowali alternatywę - Servlet API . I okazało się, że była to słuszna decyzja. Servlet API to specyfikacja, na której zbudowana jest dowolna aplikacja internetowa Java, niezależnie od tego, czy jest to aplikacja internetowa, czy usługa sieciowa, która zwraca informacje zgodnie z żądaniem. Dlatego droga do zrozumienia działania aplikacji internetowych Java zaczyna się od zrozumienia Servlet API.
Aplikacja internetowa w Javie – 2

API serwletów

Zatem Servlet API jest tym, co twórcy języka zaoferowali programistom Java. Servlet API to specyfikacja, która powinna odpowiedzieć na nasze główne pytania. Można go znaleźć tutaj: „ JSR-000340 JavaTM Servlet 3.1 Final Release for Evaluation ”. Rozdział „ 1.1 Czym jest serwlet? ” mówi, że serwlet to komponent sieciowy oparty na technologii Java, który tworzy dynamiczną treść (czyli treść). „Oparty na Javie” oznacza, że ​​serwlet jest klasą Java skompilowaną do kodu bajtowego . Serwlety są zarządzane przez kontener serwletów, czasami nazywany silnikiem serwletów. Kontener serwletów to rozszerzenie serwera WWW udostępniające funkcjonalność serwletu. Z kolei serwlety zapewniają interakcję z klientem w paradygmacie żądanie/odpowiedź, który jest realizowany przez kontener serwletów. W rozdziale „ 1.2 Co to jest kontener serwletów? ” jest powiedziane, że kontener serwletów jest częścią serwera WWW lub serwera aplikacji, który zapewnia usługi sieciowe, za pośrednictwem których wysyłane są żądania i odpowiedzi, generowane i przetwarzane są żądania i odpowiedzi oparte na MIME . Ponadto kontenery serwletów zarządzają cyklem życia serwletów (tj. decydują, kiedy je utworzyć, usunąć itp.). Aby odbierać żądania i wysyłać odpowiedzi, wszystkie kontenery serwletów muszą obsługiwać protokół HTTP. Tutaj chciałbym dodać, że MIME to standard, specyfikacja mówiąca, w jaki sposób należy kodować informacje i formatować wiadomości, aby można je było przesyłać przez Internet.
Aplikacja internetowa w Javie – 3

Serwer internetowy

Serwer WWW to serwer, który akceptuje żądania HTTP od klientów i udostępnia im odpowiedzi HTTP (zwykle wraz ze stroną HTML, obrazem, plikiem lub innymi danymi). Żądane zasoby są identyfikowane poprzez adresy URL. Jednym z najpopularniejszych serwerów WWW obsługujących Servlet API jest Apache Tomcat . Większość serwerów internetowych to złożone maszyny składające się z różnych komponentów, z których każdy wykonuje określone funkcje. Na przykład:
Aplikacja internetowa w Javie - 4

Złącza

— Na wejściu mamy konektory (tj. konektory), które akceptują przychodzące żądania od klientów. Złącze HTTP w Tomcat jest implementowane przy użyciu komponentu „Coyote”. Konektory odbierają dane od klienta i przekazują je do silnika Tomcat. Kontener serwletów - Tomcat Engine z kolei przetwarza żądanie otrzymane od klienta za pomocą komponentu „Catalina”, będącego kontenerem serwletów. Więcej szczegółów można znaleźć w dokumentacji Tomcat: „ Przegląd architektury ”. Istnieją inne serwery internetowe obsługujące specyfikację Servlet API. Na przykład „ Molo ” lub „ Pod prądem ”. Ich architektura jest podobna, więc rozumiejąc zasadę pracy z jednym kontenerem serwletów, można przejść do pracy z innym.
Aplikacja internetowa w Javie – 5

Aplikacja internetowa

A zatem, abyśmy mogli uruchomić aplikację internetową, potrzebujemy serwera WWW obsługującego Servlet API (to znaczy takiego, który posiada komponent rozszerzenia implementujący obsługę Servlet API dla serwera WWW). Cienki. Czym w ogóle jest aplikacja internetowa? Zgodnie z rozdziałem „ 10 aplikacji internetowych ” specyfikacji Servlet API, aplikacja internetowa to zbiór serwletów, stron HTML, klas i innych zasobów, które tworzą ostateczną aplikację na serwerze sieciowym. Zgodnie z rozdziałem „ 10.6 Plik archiwum aplikacji sieciowych ”, aplikacja internetowa może zostać spakowana w Web ARchive (archiwum z rozszerzeniem WAR). Jak stwierdzono na stronie „ Słownik-219 ”:
Aplikacja internetowa w Javie - 6
Oznacza to, że zamiast JAR tworzony jest WAR, aby pokazać, że jest to aplikacja internetowa. Kolejny ważny fakt: musimy mieć określoną strukturę katalogów w naszym archiwum WAR. W specyfikacji Servlet API w rozdziale „ 10.5 Struktura katalogów ”. W tym rozdziale jest napisane, że istnieje specjalny katalog o nazwie „WEB-INF”. Katalog ten jest wyjątkowy pod tym względem, że nie jest widoczny dla klienta i nie jest mu pokazywany bezpośrednio, ale jest dostępny dla kodu serwletu. Mówi także, co może zawierać katalog WEB-INF:
Aplikacja internetowa w Javie - 7
Z całej tej listy nie znamy i nie rozumiemy elementu dotyczącego jakiegoś pliku web.xml zwanego deskryptorem wdrażania . Co to jest? Rozdział „ 14. Deskryptor wdrożenia ” poświęcony jest deskryptorowi wdrożenia. Krótko mówiąc, deskryptor wdrażania to plik XML opisujący sposób wdrożenia (czyli uruchomienia) naszej aplikacji internetowej na serwerze WWW. Na przykład deskryptor wdrożenia wskazuje, z jakich adresów URL należy korzystać, aby uzyskać dostęp do naszej aplikacji, wskazane są ustawienia zabezpieczeń odnoszące się do naszej aplikacji itp. Rozdział „ 14.2 Zasady przetwarzania wdrożenia ” mówi, że plik web.xml zostanie zweryfikowany pod kątem schematu przed skonfigurowaniem i uruchomieniem naszej aplikacji (tzn. zostanie sprawdzone, czy zawartość pliku web.xml jest poprawnie zapisana zgodnie ze schematem) . A w rozdziale „ 14.3 Deskryptor wdrażania ” wskazano, że diagram znajduje się tutaj: http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd Jeśli spojrzymy na zawartość pliku, możemy zobaczyć:
Aplikacja internetowa w Javie - 8
Jaki schemat jest używany w plikach XML? Schematy wskazują, jak poprawnie wypełnić dokument XML: jakich elementów można użyć, jakiego rodzaju dane można określić w elementach, w jakiej kolejności powinny się układać elementy, jakie elementy są wymagane itp. Schemat dokumentu XML można porównać z interfejsem w Javie, ponieważ schemat w Javie określa także, w jaki sposób należy pisać klasy, które spełniają dany interfejs (czyli implementują dany interfejs). Jesteśmy więc uzbrojeni w tajemną wiedzę i jesteśmy gotowi stworzyć naszą pierwszą aplikację internetową!
Aplikacja internetowa w Javie - 9

Tworzenie aplikacji internetowej

Trudno sobie wyobrazić pracę z nowoczesną aplikacją Java bez korzystania z automatycznych systemów budowania projektów. Do najpopularniejszych systemów należą Maven i Gradle . Do tej recenzji użyjemy Gradle. Instalacja Gradle jest opisana na oficjalnej stronie internetowej . Do stworzenia nowej aplikacji potrzebujemy wtyczki wbudowanej w Gradle: „ Build Init Plugin ”. Aby utworzyć aplikację Java, musisz uruchomić następującą komendę: gradle init --type java-application
Aplikacja internetowa w Javie - 10
Po utworzeniu projektu będziemy musieli dokonać edycji pliku build.gradle . Jest to tak zwany skrypt budujący (więcej szczegółów można znaleźć w dokumentacji Gradle: „ Pisanie skryptów budujących ”). Ten plik opisuje sposób składania projektu i inne aspekty pracy z projektem Java. Blok wtyczek opisuje, które „ wtyczki Gradle ” powinny zostać użyte w bieżącym projekcie Gradle. Wtyczki rozszerzają możliwości naszego projektu. Na przykład domyślną wtyczką jest „ Java ”. Ta wtyczka jest zawsze używana, jeśli potrzebujemy obsługi Java. Ale nie potrzebujemy wtyczki „ aplikacja ”, ponieważ… w jego opisie jest napisane, że służy do tworzenia „wykonywalnej aplikacji JVM”, czyli tzw. uruchamianie aplikacji JVM. Musimy stworzyć aplikację internetową w formie archiwum WAR. A jeśli poszukamy słowa WAR w dokumentacji Gradle, znajdziemy „ Wtyczkę War ”. Dlatego określimy następujące wtyczki:
plugins {
    id 'java'
    id 'war'
}
Również w " Domyślnych ustawieniach wtyczki War " jest napisane, że katalog z całą zawartością aplikacji webowej powinien być "src/main/webapp", powinien znajdować się ten sam katalog WEB-INF, w którym powinien znajdować się plik web.xml usytuowany. Stwórzmy taki plik. Wypełnimy go nieco później, bo... nie mamy jeszcze wystarczających informacji na ten temat. W bloku „zależności” wskazujemy zależności naszego projektu, czyli te biblioteki/frameworki, bez których nasza aplikacja nie może działać. W tym przypadku piszemy aplikację internetową, co oznacza, że ​​nie możemy pracować bez Servlet API:
dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    testCompile 'junit:junit:4.12'
}
ProvideCompile oznacza, że ​​zależność nie musi być uwzględniana w naszym archiwum WAR aplikacji internetowej: jest ona potrzebna jedynie do kompilacji. Po wykonaniu zależność ta zostanie zapewniona przez kogoś innego (to znaczy serwer WWW). Cóż, w skrypcie budującym zostawiamy informację z jakiego repozytorium zależności chcemy skorzystać - zostaną z niego pobrane wszystkie określone zależności:
repositories {
    jcenter()
}
Usuwamy wszystko inne z pliku skryptu kompilacji. Zmodyfikujmy teraz klasę src\main\java\App.java. Zróbmy z tego serwlet. Specyfikacja API serwletu w rozdziale „ ROZDZIAŁ 2. Interfejs serwletu ” stwierdza, że ​​interfejs serwletu ma podstawową implementację HttpServlet , która w większości przypadków powinna wystarczyć, a programiści muszą jedynie po niej dziedziczyć. Natomiast w rozdziale „ 2.1.1 Specyficzne metody obsługi żądań HTTP ” wskazane są główne metody przetwarzania przychodzących żądań. Przepiszmy zatem klasę App.java:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.IOException;

public class App extends HttpServlet {
    public String getGreeting() {
        return "Hello world.";
    }

    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 		// https://www.oracle.com/technetwork/java/servlet-142430.html
 		PrintWriter out = resp.getWriter();
 		out.println(this.getGreeting());
 		out.close();
 	}
}
Wygląda więc na to, że wszystko mamy gotowe. Pozostaje tylko poprawnie napisać deskryptor wdrożenia. Skopiuj następujący tekst z diagramu do pliku web.xml:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="..."
      version="3.1">
      ...
</web-app>
A także ścieżka do schematu, która jest w nim wskazana: http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd Przyjrzyjmy się teraz przykładowi, jak powinien wyglądać plik web.xml w specyfikacji Servlet API. Przykład ten podano w rozdziale „ 14.5.1 Przykład podstawowy ”. Połączmy to co pokazano na schemacie z przykładem wskazanym w specyfikacji. Otrzymujemy co następuje:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
      version="3.1">
      <display-name>A Simple Web Application</display-name>
      <servlet>
		<servlet-name>app</servlet-name>
		<servlet-class>App</servlet-class>
	  </servlet>
	  <servlet-mapping>
		<servlet-name>app</servlet-name>
		<url-pattern>/app</url-pattern>
	  </servlet-mapping>
</web-app>
Jak widać, użyliśmy określonego wcześniej schematu i schemaLocation. Natomiast opis samych elementów zaczerpnięto z przykładu z rozdziału 14.5.1. Jeśli wszystko zrobiliśmy poprawnie, zadanie Gradle War wykonamy bez błędów:
Aplikacja internetowa w Javie - 11
Aplikacja internetowa w Javie - 12

Uruchomienie aplikacji internetowej

Jak uruchamia się aplikacja internetowa? Zajmijmy się najpierw bardziej złożoną opcją. Powiedzieliśmy wcześniej, że istnieje serwer WWW Apache Tomcat obsługujący API serwletów. Oznacza to, że możemy wdrożyć nasze zebrane archiwum wojenne (mówią też „wdrożyć”) na tym serwerze. Na stronie „ Pobierz Tomcat ” pobierz z sekcji „Dystrybucje binarne” typ dostawy „Core” w formacie zip. I rozpakuj pobrane archiwum do jakiegoś katalogu, na przykład C:\apache-tomcat-9.0.14. Przed uruchomieniem serwera otwórzmy plik do edycji conf\tomcat-users.xmli dodajmy do niego następującą linię: <user username="tomcat" password="tomcat" roles="tomcat,manager-gui,admin-gui"/> Teraz w linii poleceń przejdź do katalogu bin i wykonaj catalina.bat start. Domyślnie konsola serwera będzie dostępna pod adresem http://localhost:8080/manager. Login i hasło są takie same, jak te, które podaliśmy w tomcat-users.xml. Tomcat ma katalog „webapps”, który zawiera aplikacje internetowe. Jeśli chcemy wdrożyć własne, musimy skopiować tam nasze archiwum wojenne. Kiedy wcześniej uruchamialiśmy polecenie gradle war, \build\libs\w katalogu utworzono archiwum wojny. To właśnie musimy skopiować. Po skopiowaniu odśwież stronę http://localhost:8080/manageri zobacz:
Aplikacja internetowa w Javie - 13
Po zakończeniu http://localhost:8080/javaweb/appprzejdziemy do naszego serwletu, ponieważ Wcześniej „mapowaliśmy” (to znaczy mapowaliśmy) żądanie /app na serwlet aplikacji. Istnieje szybszy sposób sprawdzenia działania aplikacji. System montażu znów nam w tym pomaga. W skrypcie kompilacji naszego projektu Gradle możemy dodać nową wtyczkę „ Gretty ” do sekcji wtyczek: id "org.gretty" version "2.3.1" A teraz możemy wykonać zadanie gradle, aby uruchomić naszą aplikację:gradle appRun
Aplikacja internetowa w Javie - 14
Aby uzyskać szczegółowe informacje , zobacz „ Dodawanie wtyczki gretty i uruchamianie aplikacji ”.
Aplikacja internetowa w Javie - 15

Spring i API serwletów

Serwlety są podstawą wszystkiego. Nawet teraz popularny Spring Framework to nic innego jak dodatek do Servlet API. Zacznijmy od tego, że Spring Framework jest nową zależnością dla naszego projektu. Dlatego dodajmy go do skryptu budującego w bloku zależności: compile 'org.springframework:spring-webmvc:5.1.3.RELEASE' W dokumentacji Spring Framework znajduje się rozdział " 1.1. DispatcherServlet ". Mówi, że Spring Framework jest zbudowany na wzorcu „front kontrolera” - wtedy istnieje centralny serwlet o nazwie „ DispatcherServlet ”. Wszystkie żądania przychodzą do tego serwletu, który deleguje wywołania do niezbędnych komponentów. Widzisz, nawet tutaj są serwlety. Musisz dodać słuchacza do deskryptora wdrożenia:
<listener>
	&ltlistener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
To jest detektor zdarzeń kontekstu serwletu. Oznacza to, że kiedy uruchamiany jest kontekst serwletu, uruchamiany jest także kontekst wiosenny (WebApplicationContext). Co to jest kontekst serwletu? Jest to opisane w specyfikacji Servle API w rozdziale " ROZDZIAŁ 4. Kontekst serwletu ". Kontekst serwletu to „widok” aplikacji internetowej, w której działają serwlety. Każda aplikacja internetowa ma swój własny kontekst serwletu. Następnie, aby włączyć Spring Framework, musisz określić parametr-kontekstu - parametr inicjujący dla kontekstu serwletu.
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
Definicja DispatcherServlet kończy konfigurację :
<servlet>
	<servlet-name>app</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
 		<param-value></param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
	<servlet-name>app</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>
A teraz musimy tylko wypełnić plik określony w kontekścieConfigLocation. Jak to zrobić opisano w dokumentacji Spring Framework w rozdziale „1.3.1. Deklaracja”:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="ru.javarush.javaweb"/>
    <mvc:annotation-driven/>
</beans>
Ważne jest tutaj nie tylko wskazanie, który pakiet ma zostać przeskanowany, ale także to, że chcemy sterować adnotacjami, czyli kontrolować adnotacje dotyczące działania Springa. Pozostaje tylko utworzyć pakiet ru.javarush.javaweb i umieścić w nim klasę kontrolera Spring:
package ru.javarush.javaweb;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SpringController {

    @GetMapping("/app")
    @ResponseBody
    public String getGreeting() {
        return "Hello world.";
    }
}
Teraz uruchamiając gradle appRun i idąc pod wskazany adres http://127.0.0.1:8080/javaweb/appotrzymamy to samo Hello World. Jak widać, Spring Framework jest ściśle powiązany z API serwletów i wykorzystuje go do pracy nad nim.
Aplikacja internetowa w Javie - 16

Adnotacje

Jak widzieliśmy, adnotacje są wygodne. I nie tylko my tak myśleliśmy. Dlatego w specyfikacji Servlet API, począwszy od wersji 3.0, pojawił się rozdział „ ROZDZIAŁ 8 Adnotacje i możliwość podłączania ”, który precyzuje, że kontenery serwletów muszą obsługiwać możliwość określenia tego, co zostało wcześniej określone w deskryptorze wdrażania poprzez adnotacje. W ten sposób plik web.xml można całkowicie usunąć z projektu, a nad klasą serwletu można umieścić adnotację @WebServlet i wskazać ścieżkę, na którą ma zostać zmapowany serwlet. Tutaj wszystko wydaje się jasne. A co jeśli podłączymy Springa do projektu, który wymaga bardziej skomplikowanych ustawień? Tutaj wszystko jest trochę bardziej skomplikowane. Po pierwsze, dokumentacja Springa mówi, że aby skonfigurować Springa bez web.xml, musisz użyć własnej klasy, która zaimplementuje WebApplicationInitializer. Więcej szczegółów znajdziesz w rozdziale " 1.1. DispatcherServlet ". Okazuje się, że jest to klasa wiosenna. W jaki sposób zatem używany jest tutaj interfejs API serwletu? W rzeczywistości ServletContainerInitializer został dodany do Servlet API 3.0 . Używając specjalnego mechanizmu w Javie (zwanego SPI ), Spring określa swój inicjator kontenera serwletu o nazwie SpringServletContainerInitializer. Z kolei szuka już implementacji WebApplicationInitializer i wywołuje niezbędne metody oraz dokonuje niezbędnych ustawień. Aby uzyskać więcej informacji, zobacz „ Jak kontener serwletów znajduje implementacje WebApplicationInitializer ”. Powyższe ustawienia można wykonać w następujący sposób:
package ru.javarush.javaweb.config;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class AppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        // регистрируем конфигурацию созданую высше
        ctx.register(AppConfig.class);
        // добавляем в контекст слушателя с нашей конфигурацией
        servletContext.addListener(new ContextLoaderListener(ctx));

        ctx.setServletContext(servletContext);

        // настраиваем маппинг Dispatcher Servlet-а
        ServletRegistration.Dynamic servlet =
                servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
    }
}
Teraz korzystając z " Konfiguracji opartej na Javie " wskażemy który pakiet przeskanować + włączymy adnotacje:
package ru.javarush.javaweb.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "ru.javarush.javaweb.controllers")
public class AppConfig {
}
A sam SpringController został przeniesiony do ru.javarush.javaweb.controllers, żeby przy skanowaniu konfiguracja nie znalazła się sama, a jedynie szukała kontrolerów.
Aplikacja internetowa w Javie - 17

Zreasumowanie

Mam nadzieję, że ten przegląd rzucił trochę światła na działanie aplikacji internetowych w Javie. To dopiero wierzchołek góry lodowej, jednak bez zrozumienia podstaw trudno zrozumieć, jak działają technologie oparte na tym fundamencie. Interfejs API serwletów jest centralną częścią każdej aplikacji internetowej Java i sprawdziliśmy, jak pasują do niego inne frameworki. Aby kontynuować, możesz obejrzeć następujące materiały: #Wiaczesław
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION