Come testare una WebApp in Spring Boot senza main()

di il
29 risposte

29 Risposte - Pagina 2

  • Re: Come testare una WebApp in Spring Boot senza main()

    giannino1995 ha scritto:


    @
    Si ho capito @Conditional ma avrei preferito un'applicazione concreta capace di mostrare il funzionamento nell'annotazione.
    Lo puoi benissimo fare tu come "esercitazione".

    Quel progetto del chapter3 comunque mostra il funzionamento. Basta lanciare i due comandi di test indicati e la differenza, sebbene minima, ti assicuro che si vede.

    giannino1995 ha scritto:


    Potrei sostituire i profili con le condizioni e precisamente potrei dire a Spring Boot di verificare la presenta di MySQL sul mio notebook. Nel caso il DBMS fosse assente potrei dire a Spring Boot di avviare H2.
    No attenzione ad una cosa: non sei tu che puoi decidere di far "avviare" un db. Dietro l'avvio del db c'è moltissima logica in Boot grazie anche agli starter, pensa solo alla inizializzazione di datasource, connection pool e altro. Che è parecchio nascosta e che non potresti certo replicare facilmente.

    Ci penso domani e ti indico uno scenario di esercizio che sia (spero mi venga in mente) utile.
  • Re: Come testare una WebApp in Spring Boot senza main()

    Nella stessa cartella in cui si trova Application.java ho aggiunto le classi seguenti ma poi non so come agganciarle al progetto in Spring Boot:
    
    package apress.hellospringboot;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Profile;
    @SpringBootApplication
    public class Application {
    	public static void main(String[] args) {
    		SpringApplication.run(Application.class, args);
    	}
    }
    
    
    /**
     *
     */
    package apress.hellospringboot;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import org.springframework.context.annotation.Conditional;
    
    /**
     * @author Siva
     *
     */
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Conditional(DatabaseTypeCondition.class)
    public @interface DatabaseType
    {
    	String value();
    }
    
    
    /**
     *
     */
    package apress.hellospringboot;
    
    import java.util.Map;
    
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    /**
     * @author Siva
     *
     */
    public class DatabaseTypeCondition implements Condition
    {
        @Override
        public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
        {
            Map<String, Object> attributes = metadata.getAnnotationAttributes(DatabaseType.class.getName());
            String type = (String) attributes.get("value");
            String enabledDBType = System.getProperty("dbType","MYSQL");
            return (enabledDBType != null && type != null && enabledDBType.equalsIgnoreCase(type));
        }
    }
    
    Inoltre vorrei sapere se:
    String enabledDBType = System.getProperty("dbType","MYSQL");
    legge una variabile dbType da application.properties settata in questo modo:
    dbType=MySQL
    oppure no.
    Tra i tanti tentativi ho pensato a questo:
    
    package apress.hellospringboot;
    import org.springframework.context.annotation.*;
    
    @Configuration
    @ComponentScan
    public class AppConfig {
    
    	@DatabaseType("MYSQL")
    	@Profile("prod")
    	public void UsaMySQL() {
    		System.out.print("E' stato avviato MySQL come DBMS.");
    	}
    
    	@Bean
    	@DatabaseType("H2")
    	public void UsaH2() {
    		System.out.print("E' stato avviato H2 come DBMS.");
    	}
    }
    
    Ma poi come e dove avvio AppConfig.java. Inoltre non potrebbe funzionare neanche morto perché scrivendo spring.profiles.active=prod mi gioco il fatto di usare H2. Dovrebbe esistere un sistema per fare questo:
    @Configuration
    @ComponentScan
    public class AppConfig {
    
    	@DatabaseType("MYSQL")
    	public void UsaMySQL() {
    		-> Usa application.properties
    	}
    
    	@Bean
    	@DatabaseType("H2")
    	public void UsaH2() {
    		-> Usa application.prod-properties
    	}
    }
    
    Però poi resta il problema di capire come e dove avviare AppConfig.java.
  • Re: Come testare una WebApp in Spring Boot senza main()

    giannino1995 ha scritto:


    Inoltre vorrei sapere se:
    String enabledDBType = System.getProperty("dbType","MYSQL");
    legge una variabile dbType da application.properties
    No, le System Property di Java sono una cosa mentre i valori nel application.properties non diventano delle System Property ma rappresentano un contesto, ambiente a parte.

    giannino1995 ha scritto:


    Tra i tanti tentativi ho pensato a questo:
    
    @Configuration
    @ComponentScan
    public class AppConfig {
    
    	@DatabaseType("MYSQL")
    	@Profile("prod")
    	public void UsaMySQL() {
    		System.out.print("E' stato avviato MySQL come DBMS.");
    	}
    
    	@Bean
    	@DatabaseType("H2")
    	public void UsaH2() {
    		System.out.print("E' stato avviato H2 come DBMS.");
    	}
    }
    
    Una classe @Configuration fatta così non ha senso e non serve a nulla. Una classe @Configuration serve tipicamente per fare altre impostazioni con delle annotation e/o per definire altri bean. Se metti @Bean ad un metodo, ci si aspetta che restituisca un oggetto. Non può essere un metodo void ! E lì dentro non ha senso farci solo un println ...

    giannino1995 ha scritto:


    Però poi resta il problema di capire come e dove avviare AppConfig.java.
    Non devi "avviare" un bel nulla con le classi @Configuration. Vengono "pescate" in automatico (come @Component, @Controller ecc...) da Spring se sono nei package considerati dal classpath scanning.

    In Spring Boot per default il classpath scanning scansiona il package della classe principale (quella che ha @SpringBootApplication) e tutti i sottopackage. Lo dice anche bene il libro al capitolo 2, pagina 31:

    It is highly recommended that you put the main entry point class in the root package, say in com.mycompany.myproject, so that the @EnableAutoConfiguration and @ComponentScan annotations will scan for Spring beans, JPA entities, etc., in the root and all of its sub-packages automatically.
  • Re: Come testare una WebApp in Spring Boot senza main()

    Se non si può fare lascio stare. A tuo avviso sarebbe possibile scrivere in index.html il nome del database in uso con @Conditional?
    Io farei così con le prime due classi ma poi come scrivo in index.html?
    
    /**
     *
     */
    package apress.hellospringboot;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import org.springframework.context.annotation.Conditional;
    
    /**
     * @author Siva
     *
     */
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Conditional(DatabaseTypeCondition.class)
    public @interface DatabaseType
    {
    	String value();
    }
    
    
    /**
     *
     */
    package apress.hellospringboot;
    
    import java.util.Map;
    
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    /**
     * @author Siva
     *
     */
    public class DatabaseTypeCondition implements Condition {
        @Override
        public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
            Map<String, Object> attributes = metadata.getAnnotationAttributes(DatabaseType.class.getName());
            String type = (String) attributes.get("value");
            return (type != null && type.equalsIgnoreCase("MySQL"));
        }
    }
    
    In buona sostanza comprendo l'inizio dell'annotazione ma non la file. Saresti in grado di spiegarmi?
    Vorrei scrivere qualcosa in index.html, qualche uso concreto e pratico di @Conditional.
    AppConfig.java non saprei come scriverla.
    Questa classe IntelliJ me la segna sbagliata:
    
    package apress.hellospringboot;
    
    import org.springframework.context.annotation.*;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Configuration
    @ComponentScan
    public class AppConfig {
    
    	@DatabaseType("MYSQL")
    	@RequestMapping("/")
    	public String ScriviMySQL(Model model) {
    		model.addAttribute("dbType", "MySQL");
    		return "index.html";
    	}
    
    	@Bean
    	@DatabaseType("H2")
    	@RequestMapping("/")
    	public String ScriviH2(Model model) {
    		model.addAttribute("dbType", "H2");
    		return "index.html";
    	}
    
    }
    
    Questo approccio non funziona:
    
    package apress.hellospringboot;
    
    import apress.hellospringboot.domain.Database;
    import org.springframework.context.annotation.*;
    import org.springframework.ui.Model;
    
    @Configuration
    @ComponentScan
    public class AppConfig {
    
    	@Conditional(DatabaseTypeCondition.class)
    	public void ScriviMySQL(Model model) {
    		model.addAttribute("db", (new Database("MySQL")).getDatabase());
    	}
    
    }
    
    
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" 
    	  xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="utf-8"/>
    <title>Home</title>
    </head>
    <body>
    	<!-- La variabile "app.title" è fornita da messages.properties. -->
    	<h1 th:text="#{app.title}">Titolo</h1>
    	<h2 th:text="#{app.descrizione}">Descrizione</h2>
    	<h3 th:text="#{app.sottotitolo}">Sottotitolo</h3>
    	<table>
    		<thead>
    			<tr>
    				<th>Id</th>
    				<th>Name</th>
    			</tr>
    		</thead>
    		<tbody>
    		<!-- 'users' viene fornita da HomeController.java. -->
    			<tr th:each="user : ${users}">
    				<!-- 'user' è il nome della tabella, 'id' il nome del campo della tabella 'user'. -->
    				<td th:text="${user.id}">Id</td>
    				<!-- 'user' è il nome della tabella, 'name' il nome del campo della tabella 'user'. -->
    				<td th:text="${user.name}">Name</td>
    			</tr>
    		</tbody>
    	</table>
    <p th:text="db">Tipo di database</p>
    </body>
    </html>
    
    
    package apress.hellospringboot.domain;
    
    public class Database {
    
        private String database;
    
        public Database() {
        }
    
        public Database(String database) {
            this.database = database;
        }
    
        public String getDatabase() {
            return database;
        }
    
        public void setDatabase(String database) {
            this.database = database;
        }
    
    }
    
    Sono nuovamente fermo...
  • Re: Come testare una WebApp in Spring Boot senza main()

    giannino1995 ha scritto:


    Se non si può fare lascio stare.
    Non si può fare ... cosa? Non ho capito cosa pensi non si possa fare ...

    giannino1995 ha scritto:


    A tuo avviso sarebbe possibile scrivere in index.html il nome del database in uso con @Conditional?
    Ma questo non c'entra niente con i conditional ... almeno non direttamente.

    giannino1995 ha scritto:


    Io farei così con le prime due classi ma poi come scrivo in index.html?
    .html è un file "statico", per usarlo come template bisogna usare un template engine. Spring Boot può usare diversi template engine:

    - Thymeleaf
    - Mustache
    - FreeMarker
    - Groovy (che è un linguaggio ma si può fare un template es. html usando un apposito DSL)

    Quale intenderesti usare? Ti consiglio Thymeleaf ma solo perché l'ho già usato. E per usarlo devi mettere nel pom l'apposito "starter" per Thymeleaf.

    Prendi spunto magari dal mio esempio (lo sto aggiornando/ristrutturando proprio in questi ultimi giorni): spring-boot2-web-thymeleaf-sample

    giannino1995 ha scritto:


    
    @Configuration
    @ComponentScan
    public class AppConfig {
    
    	@DatabaseType("MYSQL")
    	@RequestMapping("/")
    	public String ScriviMySQL(Model model) {
    		model.addAttribute("dbType", "MySQL");
    		return "index.html";
    	}
    
    	@Bean
    	@DatabaseType("H2")
    	@RequestMapping("/")
    	public String ScriviH2(Model model) {
    		model.addAttribute("dbType", "H2");
    		return "index.html";
    	}
    
    }
    
    Mi spiace ma non ha assolutamente alcun senso!! Hai messo dei @RequestMapping, combinati con un @Bean oltretutto in una classe @Configuration.

    I metodi per gestire le request (con @RequestMapping, @GetMapping ecc...) vanno messi nei controller!!

    E il @ComponentScan non ti serve con Spring Boot perlomeno se si usa il classpath scanning predefinito come ho citato prima.


    Temo proprio che non hai compreso i conditional né come strutturare a livello basilare una applicazione Spring Boot.
  • Re: Come testare una WebApp in Spring Boot senza main()

    Ho caricato sul mio IntelliJ il tuo progetto. Si uso Thymeleaf. Adesso analizzo nel dettaglio il tuo script. Saresti in grado di implementare un Conditional sul tuo lavoro di qualunque natura (esempio: se i secondi della data sono pari fai questo oppure fai altro...). Ti confermo che non ho compreso i conditional né come strutturare a livello basilare una applicazione Spring Boot, quindi non temere ma stanne assolutamente certo.
  • Re: Come testare una WebApp in Spring Boot senza main()

    [OT]
    andbin santo subito
    [/OT]
  • Re: Come testare una WebApp in Spring Boot senza main()

    giannino1995 ha scritto:


    esempio: se i secondi della data sono pari fai questo oppure fai altro...
    No i conditional di Spring NON funzionano così e per cose di quel tipo.

    I conditional servono per attivare o non attivare un bean di Spring in base ad una certa condizione. Se hai un service es.
    @Service
    public class MyService {
        //......
    }
    Se il classpath scanning di Spring arriva al package di questa classe, trova MyService e lo "tira su" come bean Spring ("singleton", che è lo scope predefinito). Incondizionatamente, cioè di per sé non lo puoi impedire.

    Con i conditional invece puoi abilitarlo o meno. Se uso uno dei conditional GIÀ implementati da Spring Boot (non devi definire nulla) del tipo:
    @Service
    @ConditionalOnJava(range = ConditionalOnJava.Range.EQUAL_OR_NEWER, value = JavaVersion.TEN)
    public class MyService {
        //......
    }
    Allora il ragionamento di Spring è differente: tiro su il bean MyService MA solo se la versione di Java è >= 10.

    Oppure
    @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
    Allora: tiro su il bean MA solo se in classpath si trova la classe com.mysql.cj.jdbc.Driver.

    Tutto questo, ripeto, vale SOLO per i bean di Spring: tutte le classi annotate @Component, @Configuration, @Controller, @Service ecc... e i metodi @Bean contenuti in essi. Non puoi usarlo per tue classi/metodi qualunque che ti pare.
    E questa logica viene applicata SOLO all'inizio, quando il container di Spring sta partendo e viene inizializzato. Una volta che Spring ha completato la inizializzazione, i bean nel suo container sono quelli che ha trovato in base allo scanning, alle varie definizioni e agli eventuali conditional. E basta.
  • Re: Come testare una WebApp in Spring Boot senza main()

    Intanto grazie per la webapp, l'ho letta tutta con cura e ci sono alcune domande che vorrei porti:
    home.html
    01-Perché nella pagina home.html scrivi th:href="@{/reset.css}" e non href="/reset.css"? C'è qualche vantaggio nell'indicare i link con Thymeleaf?
    AppInfoService.java
    02-Perché "private RuntimeMXBean runtimeMXBean;" non ha un'annotazione mentre "private ServletContext servletContext;" si?
    03-Non capisco bene il concetto di "iniettare" del framework Sprig Boot, ho letto qualcosa ma ho sempre qualche dubbio al riguardo. Sapresti spiegarmelo con termini più semplici possibili?
    04-Dalla documentazione ufficiale leggo che @PostConstruct viene utilizzata su un metodo che deve essere eseguito dopo l'iniezione di dipendenza ma non comprendo cosa significa "dopo l'iniezione di dipendenza" e soprattutto perché initialize() non può fornire le informazioni relative alla JV dopo che la Java VM è avviata. Se le informazioni relative alla VM si potessero leggere anche dopo si userebbe @Autowired?
    HomeController.java
    05-Come detto in precedenza non comprendo questa annotazione:
    @Autowired
    private AppInfoService appInfoService;
    fino in fondo. C'è qualcosa di oscuro che mi lascia dubbioso.
    06 - Da questo esempio non saprei come utilizzare @Conditional, non saprei neppure cosa potrei fare con questa applicazione, zero totale, buio assoluto! Provo a leggere con più cura il tuo ultimo messaggio e domani ti faccio sapere.
  • Re: Come testare una WebApp in Spring Boot senza main()

    Le @Conditional di Spring Boot sono semplici. Ho aggiunto al tuo codice questo:
    
    @Service
    @ConditionalOnJava(range = ConditionalOnJava.Range.EQUAL_OR_NEWER, value = JavaVersion.EIGHT)
    public class AppInfoService {
    
    Se scrivo .NINE l'applicazione in effetti va in errore (sto usando JDK 1.8). Comincio a capire...
  • Re: Come testare una WebApp in Spring Boot senza main()

    giannino1995 ha scritto:


    home.html
    01-Perché nella pagina home.html scrivi th:href="@{/reset.css}" e non href="/reset.css"? C'è qualche vantaggio nell'indicare i link con Thymeleaf?
    Perché è una sintassi che tratta in modo speciale gli url. C'è la documentazione di Thymeleaf: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html e in particolare, sezione 4.4 Link URLs.

    In particolare @{/reset.css} il "/" iniziale è relativo alla context-root. In Spring Boot il context path predefinito è "/", ovvero la root risponde direttamente su http://host:porta/

    Ma se si imposta diversamente (nel application.properties), la context-root della applicazione potrebbe essere ad esempio http://host:porta/nomeapp/

    Usando @{/reset.css} Thymeleaf emette in output anche il context-path quindi il url è corretto qualunque sia il context-path. Se invece metti un url "crudo" a livello HTML, il "/" è relativo alla SERVER-root ovvero solo http://host:porta/ e dovresti "schiantare" nel html il context-path. Che NON è bello.

    giannino1995 ha scritto:


    AppInfoService.java
    02-Perché "private RuntimeMXBean runtimeMXBean;" non ha un'annotazione mentre "private ServletContext servletContext;" si?
    Perché il ServletContext è un oggetto particolare che Spring fa diventare anche un bean Spring e quindi è iniettabile.

    Il RuntimeMXBean non ho voluto/potuto renderlo un bean perché dava problemi ma è sicuramente perché RuntimeMXBean è relativo alla tecnologia JMX (di cui non so moltissimo).

    giannino1995 ha scritto:


    03-Non capisco bene il concetto di "iniettare" del framework Sprig Boot, ho letto qualcosa ma ho sempre qualche dubbio al riguardo. Sapresti spiegarmelo con termini più semplici possibili?
    Allora è un bel GROSSO problema per te, perché questa è la BASE di Spring, ovvero i concetti di Inversion of Control e Dependency Injection.

    giannino1995 ha scritto:


    04-Dalla documentazione ufficiale leggo che @PostConstruct viene utilizzata su un metodo che deve essere eseguito dopo l'iniezione di dipendenza ma non comprendo cosa significa "dopo l'iniezione di dipendenza" e soprattutto perché initialize() non può fornire le informazioni relative alla JV dopo che la Java VM è avviata. Se le informazioni relative alla VM si potessero leggere anche dopo si userebbe @Autowired?
    Devi studiare IoC e DI.

    giannino1995 ha scritto:


    HomeController.java
    05-Come detto in precedenza non comprendo questa annotazione:
    @Autowired
    private AppInfoService appInfoService;
    Riquoto: studiare IoC e DI.

    E non c'entra Spring Boot. C'entra Spring Framework base !!
  • Re: Come testare una WebApp in Spring Boot senza main()

    IoC e DI sono chiari. @Autowired indica a Spring quali sono le dipendenze richieste da un determinato oggetto. Le dipendenze vengono cercate nel container. RuntimeMXBean non viene cercato nel container pertanto non richiede l'annotazione @Autowired. Mi giustifico la questione in questo modo ma resta comunque strano e poco chiaro l'uso dell'annotazione. A volte si usa a volte no... sembra andare a tentoni... Il fatto che Spring Boot sia meglio di programmare con Servlet semplici forse si ma non credo uguagli in nessun modo PHP. Spring Boot è un framework completo e sicuramente potente ma ne deve ancora fare di strada per raggiungere la chiarezza e semplicità di utilizzo di PHP. Leggendo un libro ero riuscito a fare una specie di blog in un paio di mesi, con Spring Boot non so neppure fare una pagina statica e questo dice tutto.

    Thymeleaf mi piace sempre di più.

    ciao e grazie per tutto

    P.S.: Figo il banner.txt, mi sono sempre chiesto come si facesse, basta mettere un file .txt in resouces e voilà.
  • Re: Come testare una WebApp in Spring Boot senza main()

    giannino1995 ha scritto:


    IoC e DI sono chiari.

    giannino1995 ha scritto:


    ma resta comunque strano e poco chiaro l'uso dell'annotazione.
    Ma se dici che IoC e DI sono chiari ......
  • Re: Come testare una WebApp in Spring Boot senza main()

    Che cosa dovrei editare nel tuo pom.xml per rendere compatibile l'applicazione con S.B. 2.4 e JDK 1.14?
    Detto in altro modo, esiste un sistema per aggiornare il pom.xml di una webapp senza doverlo ricreare ogni volta da start.spring.io?
    Se aggiorno il numerino della versione non va...
  • Re: Come testare una WebApp in Spring Boot senza main()

    giannino1995 ha scritto:


    Che cosa dovrei editare nel tuo pom.xml per rendere compatibile l'applicazione con S.B. 2.4 e JDK 1.14?
    La versione che ho messo originalmente di S.B. cioè 2.1.3.RELEASE funziona anche con JDK 14 Oracle (provato ora), sarà perché ho usato abbastanza poco di SB nel mio sample.
    Ma mettendo l'ultima 2.3.2.RELEASE funziona comunque anche (e a maggior ragione) con JDK 14 Oracle (idem, provato ora!).
Devi accedere o registrarti per scrivere nel forum
29 risposte