home_site

Lab05 - Spring Framework, SpringBoot [ ver. 2025.04.09.008 ]

Zawartość strony

Zadania realizowane w ramach laboratorium

Skrypty opracowane w ramach zajęć.

  1. Wprowadzenie - Spring Framework
  2. Spring Framework - MVC
  3. Spring Framework - MVC Thymeleaf
  4. Spring Framework - REST (Spring Data JDBC Template)
  5. Spring AOP
  6. Spring Boot - REST (Spring Data JPA)
  7. OpenAPI - Swagger
  8. Spring Boot - baza danych H2
  1. Wprowadzenie - Spring Framework

    1. W trakcie laboratorium zapoznamy się z frameworkiem Spring w tworzeniu aplikacji MVC z szblonami tworzonymi w JSP oraz Thymeleaf, zostanie zaprezentowana funkcjonalność stylu REST w aplikacji WWW oraz wykorzystamy język apspektowy AspectJ. W ramach aplikacji wykorzystamy do zarządzania aplikacją odpowiednią klasę Javy do konfiguracji przedstawianych funkcjonalności.
    2. W pierwszej części zrealizujemy projekt z wykorzystaniem Spring Framework w ramach którego udostępnimy dane z bazy PostgreSQL poprzez funkcjonalność Spring Data JDBC w ramach architektury MVC i REST. Wymagane do realizacji projektu biblioteki zostaną dołączone z wykorzystaniem narzędzia Maven. Projekt zostanie uruchomiony na serwerze aplikacyjnym TomEE.
    3. Tworzymy projekt "Lab05c1" typu Web w narzędziu IntelliJ IDEA. Do realizacji zadań w ramach laboratorium wykorzystamy biblioteki, które dodamy z wykorzystaniem narzędzia Maven w pliku pom.xml. Lista wymaganych zależności wymaganych do realizacji projektu w perwszej cześći została przedstawiona poniżej.

      Wymagane zależności w pliku pom.xml - ( [listing dokumentu] [link do dokumentu]

                 <!-- Spring Framework -->
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-core</artifactId>
                  <version>6.2.5</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-context</artifactId>
                  <version>6.2.5</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-aop</artifactId>
                  <version>6.2.5</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-webmvc</artifactId>
                  <version>6.2.5</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-web</artifactId>
                  <version>6.2.5</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-beans</artifactId>
                  <version>6.2.5</version>
              </dependency>
              <!--  Spring Data -->
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-jdbc</artifactId>
                  <version>6.2.5</version>
              </dependency>
              <dependency>
                  <groupId>org.postgresql</groupId>
                  <artifactId>postgresql</artifactId>
                  <version>42.7.5</version>
              </dependency>
              <!-- AspectJ -->
              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjrt</artifactId>
                  <version>1.9.22</version>
              </dependency>
              <dependency>
                  <groupId>org.aspectj</groupId>
                  <artifactId>aspectjweaver</artifactId>
                  <version>1.9.22</version>
              </dependency>
              <!-- Thymeleaf -->
              <dependency>
                  <groupId>org.thymeleaf</groupId>
                  <artifactId>thymeleaf</artifactId>
                  <version>3.1.2.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>org.thymeleaf</groupId>
                  <artifactId>thymeleaf-spring6</artifactId>
                  <version>3.1.2.RELEASE</version>
              </dependency>
              <!-- JSTL -->
              <dependency>
                  <groupId>org.glassfish.web</groupId>
                  <artifactId>jakarta.servlet.jsp.jstl</artifactId>
                  <version>2.0.0</version>
              </dependency>		
              <!-- Logging -->
              <dependency>
                  <groupId>org.slf4j</groupId>
                  <artifactId>slf4j-api</artifactId>
                  <version>2.0.13</version>
              </dependency>
              <dependency>
                  <groupId>org.apache.logging.log4j</groupId>
                  <artifactId>log4j-slf4j-impl</artifactId>
                  <version>2.23.1</version>
                  <scope>test</scope>
              </dependency>
              <dependency>
                  <groupId>org.slf4j</groupId>
                  <artifactId>slf4j-jdk14</artifactId>
                  <version>2.0.13</version>
                  <scope>test</scope>
              </dependency>
    4. Na początek opracujemy prostą aplikację zgodną z wzorcem MVC z wykorzystaniem framework'u Spring oraz odpowiedniej klasy konfigurującej.
      • klasa SimpleController.java prosty kontroler realizujący podstawową funkcjonalność aplikacji,
      • klasa konfigurująca aplikację WebServlet.java,
      • klasa konfigurująca framework Spring WebConfig.java,
      • skrypt widoku script01.jsp - obsługa żądania "http:// ... /labs/lab01",
      • skrypt widoku script02.jsp - obsługa żądania "http:// ... /labs/lab02/?name=imie",
    5. Klasa kontrolera realizująca funkcjonalność Spring'a.

      Klasa SimpleController.java - ( [listing dokumentu] [link do dokumentu]

      package zti.web;
      
      import java.util.HashMap;
      import java.util.Map;
      
      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RequestMethod;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.servlet.ModelAndView;
      
      @Controller(value = "saySimpleController")
      @RequestMapping("/labs")
      public class SimpleController {
      
          @RequestMapping("/lab01")
          public ModelAndView lab01() {
              Map<String, String> modelData = new HashMap<String, String>();
              modelData.put("msg", "Hello, world!");
              return new ModelAndView("script01.jsp", modelData);
          }
      
          @RequestMapping("/lab02")
          public String lab02(@RequestParam(name="name", defaultValue="Anonim") String name, Model model) {
              //Map<String, String> modelData = new HashMap<String, String>();
              //modelData.put("msg", "Hello, world!");
              model.addAttribute("mess","Hello " + name + "!");
              //return new ModelAndView("script02.jsp", modelData);
              return "script02.jsp" ;
          }
      }
    6. Klasa konfigurująca aplikację.

      Klasa WebServlet.java - ( [listing dokumentu] [link do dokumentu]

      package zti.web;
      
      import jakarta.servlet.ServletContext;
      import jakarta.servlet.ServletException;
      import jakarta.servlet.ServletRegistration;
      import org.springframework.web.WebApplicationInitializer;
      import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
      import org.springframework.web.servlet.DispatcherServlet;
      
      public class WebServlet implements WebApplicationInitializer {
          public void onStartup(ServletContext ctx) throws ServletException {
              AnnotationConfigWebApplicationContext webCtx = new AnnotationConfigWebApplicationContext();
              webCtx.register(WebConfig.class);
              webCtx.setServletContext(ctx);
              ServletRegistration.Dynamic servlet = ctx.addServlet("dispatcher", new DispatcherServlet(webCtx));
              servlet.setLoadOnStartup(1);
              servlet.addMapping("/");
          }
      }
    7. Klasa konfigurująca framework Spring'a.

      Klasa WebConfig.java - ( [listing dokumentu] [link do dokumentu]

      package zti.web;
      
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.ApplicationContextAware;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.servlet.ViewResolver;
      import org.springframework.web.servlet.config.annotation.*;
      import org.springframework.web.servlet.view.InternalResourceViewResolver;
      
      @EnableWebMvc
      @ComponentScan(basePackages = "zti")
      @Configuration
      
      public class WebConfig implements ApplicationContextAware, WebMvcConfigurer {
      
          private ApplicationContext applicationContext;
      
          @Override
          public void setApplicationContext(ApplicationContext applicationContext) {
              this.applicationContext = applicationContext;
          }
      
          @Bean
          public ViewResolver jspViewResolver() {
              InternalResourceViewResolver bean = new InternalResourceViewResolver();
              bean.setPrefix("/WEB-INF/views/jsp/");
              bean.setViewNames("*.jsp");
              return bean;
          }
      
          
      }
      
    8. Skrypt widoku "script01.jsp" realizujący podstawową funkcjonalność opracowanej aplikacji (znajduje się katalogu "WEB-INF/views/jsp").
      <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
      
      <c:out value="${msg}"/>
      
    9. Skrypt widoku "script02.jsp" realizujący podstawową funkcjonalność opracowanej aplikacji (znajduje się katalogu "WEB-INF/views/jsp").
      <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
      
      <c:out value="${mess}"/>
      
    10. Uruchamiamy aplikację. Poprawność działania sprawdzamy poniższymi poleceniami:
      • http://localhost:<port>/<context>/labs/lab01 - pierwsza odpowiedź opracowanej aplikacji
      • http://localhost:<port>/<context>/labs/lab02?name=Adam - odczyt danych z QUERY_STRING
  2. Spring Framework - realizacja wzorca MVC (funkcjonalność CRUD, Spring JDBC Template)

    1. W ramach tego zadania opracujemy aplikację realizującą polecenia CRUD do wcześniej opracowanej bazy danych w DBaaS ElephantSQL. Dostęp do bazy danych zostanie zrealizowany z wykorzystaniem interfejsu Spring'a - JDBCTemplate i wcześniej przygotowanego wpisu JNDI do bazy danych DBaaS.
    2. Do realizacji zadania wykorzystamy poniższe klasy oraz dokonamy modyfikacji w klasie konfigurującej Spring:
      • klasa MvcJDBC.java - kontroler realizujący funkcjonalność aplikacji,
      • klasa Person.java - klasa POJO opisująca dane w tabeli "Person",
      • interfejs PersonDao.java - interfejs opisujący metody dostępu do tabeli "Person",
      • klasa PersonDaoImpl.java - klasa implementująca interfejs "PersonDao",
      • skrypt widoku list.jsp - obsługa żądania "http:// ... /mvc/list",
    3. Tworzymy klasę POJO opisującą tabelę "Person" (umieszczamy w pakiecie "model").

      Klasa Person.java - ( [listing dokumentu] [link do dokumentu]

      package zti.model;
      
      public class Person {
      
          private String lname ;
          private String fname ;
          private String city ;
          private String email ;
          private String tel ;
          private Integer id;
      
          public Person() {}
      
          public Person(String lname, String fname, String city, String email, String tel, Integer id){
              this.lname = lname;
              this.fname = fname;
              this.city = city;
              this.email = email;
              this.tel = tel;
              this.id = id;
          }
      
          public void setFname( String newValue ){ fname = newValue ; }
          public String getFname() { return fname ; }
          public void setLname ( String newValue ) { lname = newValue ; }
          public String getLname() { return lname ; }
          public void setCity ( String newValue ) { city = newValue ; }
          public String getCity() { return city ; }
          public void setEmail ( String newValue ) { email = newValue ; }
          public String getEmail() { return email ; }
          public void setTel ( String newValue ) { tel = newValue ; }
          public String getTel() { return tel ; }
          public void setId ( Integer newValue ) { id = newValue ; }
          public Integer getId() { return id ; }
      
      }
    4. Interfejs przedstawiający metody dostępu do tabeli "Person" (umieszczamy w pakiecie "model").

      Klasa PersonDao.java - ( [listing dokumentu] [link do dokumentu]

      package zti.model;
      
      import org.springframework.stereotype.Component;
      
      import java.util.List;
      
      
      public interface PersonDao {
      
          public List<Person> getPeople();
          public Person getPersonByID(int id);
          public int save(Person person);
          public int update(int id);
          public int delete(int id);
      
      }
    5. Implementacja interfejsu "PersonDao" (umieszczamy w pakiecie "model").

      Klasa PersonDaoImpl.java - ( [listing dokumentu] [link do dokumentu]

      package zti.model;
      
      
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Map;
      
      import javax.sql.DataSource;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.jdbc.core.BeanPropertyRowMapper;
      import org.springframework.jdbc.core.JdbcTemplate;
      import org.springframework.stereotype.Component;
      import org.springframework.stereotype.Repository;
      
      @Repository
      public class PersonDaoImpl implements PersonDao {
      
          @Autowired
          @Qualifier("dbDataSource")
      
          private DataSource dataSource;
          private JdbcTemplate jdbcTemplate;
      
      
          public void setDataSource(DataSource dataSource) {
              this.dataSource = dataSource;
      
          }
      
          public PersonDaoImpl(DataSource dataSource) {
              jdbcTemplate = new JdbcTemplate(dataSource);
          }
      
          public List<Person> getPeople() {
      
              List<Person> people = new ArrayList<Person>();
              String query = "select id, fname, lname, email, city, tel from Person";
              //jdbcTemplate = new JdbcTemplate(dataSource);
              List<Map<String,Object>> personRows = jdbcTemplate.queryForList(query);
      
              for(Map<String,Object> personRow : personRows){
                  Person person = new Person();
                  person.setId(Integer.parseInt(String.valueOf(personRow.get("id"))));
                  person.setFname(String.valueOf(personRow.get("fname")));
                  person.setLname(String.valueOf(personRow.get("lname")));
                  person.setCity(String.valueOf(personRow.get("city")));
                  person.setEmail(String.valueOf(personRow.get("email")));
                  person.setTel(String.valueOf(personRow.get("tel")));
                  people.add(person);
              }
              return people;
          }
      
          public Person getPersonByID (int id) {
              String sql = "SELECT * FROM person WHERE id=?" ;
              //jdbcTemplate = new JdbcTemplate(dataSource);
              return jdbcTemplate.queryForObject(sql,  new Object[]{id}, new BeanPropertyRowMapper<Person>(Person.class));
          }
      
          public int delete(int id) {
              String sql = "DELETE FROM person WHERE id=?" ;
              //jdbcTemplate = new JdbcTemplate(dataSource);
              return jdbcTemplate.update(sql);
          }
      
          public int save(Person person) {
              String sql = "" ;
              //jdbcTemplate = new JdbcTemplate(dataSource);
              return jdbcTemplate.update(sql);
          }
      
          public int update(int id) {
              String sql = "" ;
              //jdbcTemplate = new JdbcTemplate(dataSource);
              return jdbcTemplate.update(sql);
          }
      
      
      
      }
      
      
      
    6. Klasa kontrolera realizująca funkcjonalność modelu MVC (umieszczamy w pakiecie "web").

      Klasa MvcJDBC.java - ( [listing dokumentu] [link do dokumentu]

      package zti.web;
      
      import java.util.List;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.web.bind.annotation.RequestMapping;
      
      
      import zti.model.Person;
      import zti.model.PersonDaoImpl;
      
      @Controller(value = "MvcController")
      @RequestMapping("/mvc")
      public class MvcJDBC {
      
          @Autowired
          PersonDaoImpl personDao;
      
          @RequestMapping(value = "/list")
          public String getTable(Model model) {
              // logger.info("Start getAllEmployees.");
              List<Person> people = personDao.getPeople() ;
              model.addAttribute("table",people);
              return "list.jsp";
          }
      
      }
    7. Tworzymy skrypt JSP wyświetlający zawartość tabeli "Person" (umieszczamy w katalogu "WEB-INF/views/jsp").

      Skrypt list.jsp - ( [listing dokumentu] [link do dokumentu]

      <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>    
        
      <h1>People List</h1>  
      <table border="2" width="70%" cellpadding="2">  
      <tr><th>Id</th><th>Fname</th><th>Lname</th><th>Email</th><th>Edit</th><th>Delete</th></tr>  
         <c:forEach var="person" items="${table}">   
         <tr>  
         <td>${person.id}</td>  
         <td>${person.fname}</td>  
         <td>${person.lname}</td>  
         <td>${person.email}</td>  
         <td><a href="editemp/${person.id}">Edit</a></td>  
         <td><a href="deleteemp/${person.id}">Delete</a></td>  
         </tr>  
         </c:forEach>  
         </table>
    8. Konfiguracja połączenia z bazą danych z wykorzystaniem interfejsu JNDI.
      • Konfiguracja interfejsu JNDI.
        Umieszczamy plik context.xml w katalogu META-INF. W pliku należy umieścić dodatkowo parametr maxTotal="1", brak parametru spowoduje przerwanie działania aplikacji - za dużo połączeń w ramach "connetion pools".
        <?xml version="1.0" encoding="UTF-8"?>
        <Context>
        <Resource name="jdbc/postgres" auth="Container"
                  type="javax.sql.DataSource" driverClassName="org.postgresql.Driver"
                  url=" --url-host-- "
                  username=" --user-- " password=" --pass-- " 
                  maxTotal="1" maxIdle="1" maxWaitMillis="-1"/>
        </Context>
        
      • W pliku web.xml umieszczamy informację jak skonfigurowany został interfejs połaczenia do bazy danych.
            <resource-ref>
                <description>
                    Resource reference to a factory for java.sql.Connection
                    instances that may be used for talking to a particular
                    database that is configured in the Context
                    configuration for the web application.
                </description>
                <res-ref-name>
                    jdbc/postgres
                </res-ref-name>
                <res-type>
                    javax.sql.DataSource
                </res-type>
            </resource-ref>
        
      • Modyfikacja klasy WebConfig.java o dodatkowy bean oraz zależności.
        import org.springframework.web.servlet.view.InternalResourceViewResolver;
        import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
        import javax.sql.DataSource;
        
            @Bean
            public DataSource dbDataSource() {
                final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
                dsLookup.setResourceRef(true);
                DataSource dataSource = dsLookup.getDataSource("jdbc/postgres");
                return dataSource;
            }
        
    9. Uruchamiamy aplikację. Poprawność działania sprawdzamy poniższym poleceniem:
      • http://localhost:<port>/<context>/mvc/list - wydruk zawartości tabeli "Person"
  3. Spring Framework - realizacja wzorca MVC (funkcjonalność CRUD, Thymeleaf)

    1. W kolejnym punkcie dodamy obsługę szablonów Thymeleaf w ramach aplikacji.
      • Na początek dodajemy bean'y do klasy konfiguracyjnej WebConfig.java oraz odpowiednie zależności.
        import org.thymeleaf.spring6.ISpringTemplateEngine;
        import org.thymeleaf.spring6.SpringTemplateEngine;
        import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
        import org.thymeleaf.spring6.view.ThymeleafViewResolver;
        import org.thymeleaf.templatemode.TemplateMode;
        import org.thymeleaf.templateresolver.ITemplateResolver;
        
            @Bean
            public ViewResolver htmlViewResolver() {
                ThymeleafViewResolver resolver = new ThymeleafViewResolver();
                resolver.setTemplateEngine(templateEngine(htmlTemplateResolver()));
                resolver.setContentType("text/html");
                resolver.setCharacterEncoding("UTF-8");
                resolver.setViewNames(new String[]{"*.html"});
                return resolver;
            }
        
            private ISpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
                SpringTemplateEngine engine = new SpringTemplateEngine();
                engine.setTemplateResolver(templateResolver);
                return engine;
            }
        
            private ITemplateResolver htmlTemplateResolver() {
                SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
                resolver.setApplicationContext(applicationContext);
                resolver.setPrefix("/WEB-INF/views/html/");
                resolver.setCacheable(false);
                resolver.setTemplateMode(TemplateMode.HTML);
                return resolver;
            }
        
      • Tworzymy skrypt HTML (szablon Thymeleaf) wyświetlający zawartość tabeli "Person" (który umieszczamy w katalogu "WEB-INF/views/html").

        Skrypt list.html - ( [listing dokumentu] [link do dokumentu]

        <html xmlns:th="http://www.thymeleaf.org">
        <head>
            <!--link th:src="@{/style/app.css}" rel="stylesheet"/-->
        </head>
        <body>
        <h1>People List</h1>
        <div th:if="${not #lists.isEmpty(table)}">
            <table border="1">
                <thead><tr><th>Id</th><th>Fname</th><th>Lname</th><th>Email</th></thead>
                <tbody>
                <tr th:each="person : ${table}">
                    <td th:text="${person.id}"></td>
                    <td th:text="${person.fname}"></td>
                    <td th:text="${person.lname}"></td>
                    <td th:text="${person.email}"></td>
                </tr>
                </tbody>
            </table>
        </div></body></html>
      • Obsługa szablonu zostanie zrealizowana poprzez dodanie odpowiedniej metody w klasie MvcJDBC.java
            @RequestMapping(value = "/list2")
            public String getTable2(Model model) {
                // logger.info("Start getAllEmployees.");
                List<Person> people = personDao.getPeople() ;
                model.addAttribute("table",people);
                return "list.html";
            }
        
      • Poprawność działania sprawdzamy poniższym poleceniem:
        • http://localhost:<port>/<context>/mvc/list2 - wydruk zawartości tabeli "Person"
  4. Spring Framework - realizacja wzorca RESTful (JDBC Template)

    1. W ramach tego zadania opracujemy aplikację realizującą interfejs RESTful. W ramach zadania wykorzystamy wcześniej opracowane klasy: "Person", "PersonDao" i "PersonDaoImpl". Dostęp do bazy danych zostanie opracowany z wykorzystaniem interfejsu Spring'a - JDBCTemplate i wcześniej przygotowanego połaczenia do bazy danych Postgres.
    2. Do realizacji zadania przygotujemy klasę kontrolera REST - RestJDBC.java.

      Klasa RestJDBC.java - ( [listing dokumentu] [link do dokumentu]

      package zti.web;
      
      import java.util.List;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.*;
      
      import zti.model.Person;
      import zti.model.PersonDaoImpl;
      
      @Controller(value = "JdbcController")
      @RequestMapping("/rest")
      public class RestJDBC {
      
          @Autowired
          PersonDaoImpl dao;
      
          @RequestMapping(value = "/person", method = RequestMethod.GET)
          public @ResponseBody List<Person> getAllPerson() {
              // logger.info("Start getAllEmployees.");
              List<Person> people = dao.getPeople() ;
              return people ;
          }
      
          @RequestMapping(value= "/person/{id}", method= RequestMethod.GET)
          public @ResponseBody Person getPerson(@PathVariable("id") int id) {
              Person entity = dao.getPersonByID(id);
              return entity;
          }
      
      }
      
      
    3. Uruchamiamy aplikację (restart serwera). Poprawność działania sprawdzamy poniższym poleceniem:
      • http://localhost:<port>/<context>/rest/person - wydruk zawartości tabeli "Person"
      • http://localhost:<port>/<context>/rest/person/id - wydruk wybranego rekordu z tabeli "Person"
  5. Spring AOP, AspectJ

    1. W ramach tego zadania dodamy do naszej aplikacji obsługę aspektów realizowaną w ramach Spring'a w module Spring AOP z wykorzystaniem języka AspectJ. Realizacja zadania wymaga utworzenia klasy zawierającej punkty złączenia (join point) oraz obsługi porad (Advice) dla wskazanych punktów. W ramach Spring AOP możemy wykorzystać następujce porady:
      • Before - porada uruchamiana przed punktem złączenia. Porada może przerwać dalsze wykonywanie kodu tylko poprzez wyrzucenie wyjątku. W każdym innym przypadku proces dotrze do punktu złączenia.
      • After - porada wykonywana zawsze, niezależnie od tego w jaki sposób kończy się punkt złączenia (w jaki sposób kończy się śledzona w ramach złączenia metoda).
      • After returning - Porada jest wykonywana po tym, jak proces przejdzie przez punkt złączenia (moment wykonywania "śledzonej" metody) bez żadnych wyjątków.
      • After throwing - porada, którą ma zostać wykonana gdy uruchamiana metoda (która stanowi punkt złączenia) zostanie zakończona przez zgłoszenie wyjątku.
      • Around - porada jest uruchamiana tuż przed punktem złączenia jakim jest wykonanie metody, a następnie umożliwia obsługę tego punktu złączenia (np. niewykonanie go w momencie zaistnienia określonego warunku). Na końcu swojego działania może jeszcze wykonać operacje po zakończeniu uruchomionej metody (jeśli wcześniej nie przerwała procesu, np. nie doprowadzając do uruchomienia metody).
    2. Realizacja zadania wymaga utworzenia odpowiedniej klasy obsługującej aspekty, dodania bibliotek obsługujących AspectJ oraz utworzenia dodatkowego bean'a w klasie AppConfig.java oraz klasy opisującej funkcjonalnóść aspektów AspectLogger.java.
    3. Dodatkowe biblioteki AspectJ wymagane w projekcie (dodane w pliku pom.xml).
      <dependency>
      	<groupId>org.aspectj</groupId>
      	<artifactId>aspectjrt</artifactId>
      	<version>1.9.22</version>
      </dependency>
      <dependency>
      	<groupId>org.aspectj</groupId>
      	<artifactId>aspectjweaver</artifactId>
      	<version>1.9.22</version>
      </dependency>
      
    4. Klasa konfigurująca osbługę aspektów w ramach aplikacji AppConfig.java.

      Klasa AppConfig.java - ( [listing dokumentu] [link do dokumentu]

      package zti.web;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.EnableAspectJAutoProxy;
      import zti.aspect.AspectLogger;
      import zti.model.PersonDaoImpl;
      
      @Configuration
      @ComponentScan("zti.web")
      @EnableAspectJAutoProxy(proxyTargetClass=true)
      public class AppConfig {
      
          PersonDaoImpl personDao;
      
          @Bean
          public AspectLogger aspectLogger() {
              return new AspectLogger();
          }
      
      }
      
    5. Klasa realizująca funkcjonalność AspectJ (umieszczamy w pakiecie "aspect").

      Klasa AspectLogger.java - ( [listing dokumentu] [link do dokumentu]

      package zti.aspect;
      
      import java.util.Arrays;
      import java.util.concurrent.TimeUnit;
      
      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Before;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Pointcut;
      import org.springframework.stereotype.Component;
      
      import zti.model.Person;
      
      @Aspect
      @Component
      public class AspectLogger {
      
          @Before("execution(* zti.model.PersonDaoImpl.getPeople(..))")
          public void logBefore1(JoinPoint joinPoint) {
              System.out.println(" *** Advice Before - (logBefore1) jest wykonany !");
              System.out.println(" *** Metoda : " + joinPoint.getSignature().getName()
                      + "() rozpoczyna się od " + Arrays.toString(joinPoint.getArgs()));
          }
      
          @Pointcut("execution(* zti.model.*.*(..))")
          private void getPeople() {
          }
      
          @AfterReturning(pointcut = "getPeople()", returning = "retVal")
          public void afterReturningMethod1(JoinPoint joinPoint, Object retVal) {
              System.out.println(" *** Advice AfterReturning - (afterReturningMethod1) jest wykonany !");
              System.out.println(" *** Metoda " + joinPoint.getSignature().getName() + " zwróciła wartość : " + retVal.toString());
          }
      
      
      
          @Around("execution(* zti.model.PersonDaoImpl.getPersonByID(..))")
          public Object beforeListPerson(ProceedingJoinPoint pjp) throws Throwable {
              System.out.println(" *** Advice Around - (beforeListPerson) jest wykonawany !");;
              Object[] args = pjp.getArgs();
              if(args.length>0){
                  System.out.print(" *** Dopasowane argumenty : " );
                  for (int i = 0; i < args.length; i++) {
                      System.out.print("arg "+(i+1)+": "+args[i].toString());
                  }
              }
      
              Person result = (Person) pjp.proceed(args);
              result.setLname(result.getLname().toUpperCase());
              System.out.println(" *** Zwrócona wartość : " + result);
              return result;
          }
      
          @Pointcut("target(zti.model.PersonDaoImpl)")
          public void repositoryClassMethods() {};
      
          @Around("repositoryClassMethods()")
          public Object measureMethodExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
              long start = System.nanoTime();
              Object retval = pjp.proceed();
              long end = System.nanoTime();
              String methodName = pjp.getSignature().getName();
              System.out.print(" *** Czas wykonania " + methodName + " wynosi " +
                      TimeUnit.NANOSECONDS.toMillis(end - start) + " ms");
              return retval;
          }
      
      
      }
      
      
      
    6. Uruchamiamy aplikację. Poprawność działania sprawdzamy poniższymi poleceniemi (sprawdzamy wpisy na konsoli):
      • http://localhost:<port>/<context>/rest/person - wydruk zawartości tabeli "Person"
      • http://localhost:<port>/<context>/rest/person/id - wydruk wybranego rekordu z tabeli "Person"
  6. Spring Boot - Spring REST i Spring Data JPA

    1. W ramach tego zadania opracujemy nowy projekt w oparciu o narzędzie Spring Boot z wykorzystaniem Spring REST i Spring Data JPA. W ramach projektu wykorzystamy bazę danych PostgreSQL (DBaaS ElephantSQL). Do realiacji zadania wymagane jest dodanie odpowiednich pakietów do naszego projektu oraz utworzenie odpowiednich klas. Dodamy do projektu klasę aplikacji obsługującą intefejs REST, klasę encje, inteterfejsu CRUD oraz klasę menadżera trwałości JPA.
    2. W ramach IDEA wybieramy projekt Spring Initializr, wpisujemy nazwę projektu, wybieramy język Java, narzędzie do zależności Maven (rys.1), następnie wybieramy Developer Tools -> Spring Boot DevTools, Developer Tools -> Lombok, Web -> Spring Web, SQL -> Spring Data JPA oraz SQL -> PostgreSQL Driver (rys.2).
      Lab05_SpringBoot_1
      Rys.1 Konfiguracja aplikacji w Spring Boot ( form1 )
      Lab05_SpringBoot_2
      Rys.2 Konfiguracja aplikacji w Spring Boot ( form2 )
    3. Na początek klasa encji obsługująca tabelę Person w bazie danych - pakiet zti.zti05c2.model.

      Klasa Person.java - ( [listing dokumentu] [link do dokumentu]

      package zti.lab05c2.model;
      
      import java.io.Serializable;
      import jakarta.persistence.*;
      
      /**
       * The persistent class for the person database table.
       *
       */
      @Entity (name="person")
      @Table (name="person", schema="public")
      
      public class Person implements Serializable {
          private static final long serialVersionUID = 1L;
          private Integer id;
          private String city;
          private String email;
          private String fname;
          private String lname;
          private String tel;
      
          public Person() {
          }
      
          @Id
          @GeneratedValue(strategy=GenerationType.IDENTITY)
          public Integer getId() {
              return this.id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public String getCity() {
              return this.city;
          }
      
          public void setCity(String city) {
              this.city = city;
          }
      
          public String getEmail() {
              return this.email;
          }
      
          public void setEmail(String email) {
              this.email = email;
          }
      
          public String getFname() {
              return this.fname;
          }
      
          public void setFname(String fname) {
              this.fname = fname;
          }
      
          public String getLname() {
              return this.lname;
          }
      
          public void setLname(String lname) {
              this.lname = lname;
          }
      
          public String getTel() {
              return this.tel;
          }
      
          public void setTel(String tel) {
              this.tel = tel;
          }
      
      }
      
    4. Następnie tworzymy interfejs obsługujący encję Person poprzez interfejs CRUD Repositiory (w pakiecie zti.zti05c2).

      Klasa PersonRepository.java - ( [listing dokumentu] [link do dokumentu]

      package zti.lab05c2;
      
      import org.springframework.data.jpa.repository.JpaRepository;
      import org.springframework.stereotype.Repository;
      import zti.lab05c2.model.Person;
      
      @Repository
      public interface PersonRepository extends JpaRepository<Person, Integer> {
      
      }
    5. Do poprawnej obsługi repozytorium potrzebna jest klasa do obsługi braku rekordu w tabeli Person (w pakiecie zti.zti05c2.exception).

      Klasa PersonNotFoundException.java - ( [listing dokumentu] [link do dokumentu]

      package zti.lab05c2.exception;
      
      public class PersonNotFoundException  extends RuntimeException {
      
          public PersonNotFoundException(Integer id) {
              super("Could not find person " + id);
          }
      }
    6. Obsługa wyjątku braku danych bez komunikatu HTTP 500 zrealizujemy poprzez dodanie klasy przetwarzającej odpowiedź klasy PersonNotFoundAdvice.java na HTTP 404. (Klasa w pakiecie zti.zti05c2.exception).

      Klasa PersonNotFoundAdvice.java - ( [listing dokumentu] [link do dokumentu]

      package zti.lab05c2.exception;
      
      import org.springframework.http.HttpStatus;
      import org.springframework.web.bind.annotation.ControllerAdvice;
      import org.springframework.web.bind.annotation.ExceptionHandler;
      import org.springframework.web.bind.annotation.ResponseBody;
      import org.springframework.web.bind.annotation.ResponseStatus;
      import zti.lab05c2.model.Person;
      
      @ControllerAdvice
      class PersonNotFoundAdvice {
      
          @ResponseBody
          @ExceptionHandler(PersonNotFoundException.class)
          @ResponseStatus(HttpStatus.NOT_FOUND)
          String employeeNotFoundHandler(PersonNotFoundException ex) {
              return ex.getMessage();
          }
      }
    7. Na koniec tworzymy klasę obsługującą repozytorium poprzez interfjs REST PersonResource.java.

      Klasa PersonResource.java - ( [listing dokumentu] [link do dokumentu]

      package zti.lab05c2;
      
      import java.util.List;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.DeleteMapping;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.PutMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RestController;
      import zti.lab05c2.exception.PersonNotFoundException;
      import zti.lab05c2.model.Person;
      
      @RestController
      class PersonResource {
      
          @Autowired
          private  PersonRepository personRepository;
      
      
          // Aggregate root
          // tag::get-aggregate-root[]
          @GetMapping("/person")
          List<Person> all() {
              return personRepository.findAll();
          }
          // end::get-aggregate-root[]
      
          @PostMapping("/person")
          Person newPerson(@RequestBody Person newPerson) {
              return personRepository.save(newPerson);
          }
      
          // Single item
      
          @GetMapping("/person/{id}")
          Person one(@PathVariable Integer id) {
      
              return personRepository.findById(id)
                      .orElseThrow(() -> new PersonNotFoundException(id));
          }
      
          @PutMapping("/person/{id}")
          Person replacePerson(@RequestBody Person newPerson, @PathVariable Integer id) {
      
              return personRepository.findById(id)
                      .map(person -> {
                          person.setFname(newPerson.getFname());
                          person.setLname(newPerson.getLname());
                          return personRepository.save(person);
                      })
                      .orElseGet(() -> {
                          newPerson.setId(id);
                          return personRepository.save(newPerson);
                      });
          }
      
          @DeleteMapping("/person/{id}")
          void deletePerson(@PathVariable Integer id) {
              personRepository.deleteById(id);
          }
      }
    8. Do uruchomienia projektu wymagane jest uzupełnienie pliku application.properties znajdującego się w katalogu resources.
      spring.datasource.driverClassName= org.postgresql.Driver
      spring.datasource.url= jdbc:postgresql://<host>:5432/<dbase>
      spring.datasource.username= <dbase>
      spring.datasource.password= <password>
      spring.datasource.hikari.connection-timeout = 20000 
      spring.datasource.hikari.minimum-idle= 2 
      spring.datasource.hikari.maximum-pool-size= 3 
      spring.datasource.hikari.idle-timeout=10000 
      spring.datasource.hikari.max-lifetime= 1000 
      server.port=<port-aplikacja>
      
      spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation= true
      spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.PostgreSQLDialect
      
      # Hibernate ddl auto (create, create-drop, validate, update)
      spring.jpa.hibernate.ddl-auto= update
      
      #Port serwera Tomcat
      server.port=8088
      
    9. Uruchamiamy aplikację. Poprawność działania sprawdzamy poniższymi poleceniemi (sprawdzamy wpisy na konsoli):
      • http://localhost:<port>/<context>/person - wydruk zawartości tabeli "Person"
      • http://localhost:<port>/<context>/person/id - wydruk wybranego rekordu z tabeli "Person"
  7. OpenAPI w projekcie Spring Boot

    1. W ramach tego zadania dodamy do naszej aplikacji obsługę intefejsu OpenAPI umożliwiającego dokumentowanie i testowanie punktów końcowych aplikacji. Do realizacji zadania wymagane jest dodanie odpowiednich pakietów do naszego projektu.
    2. Dodatkowe biblioteki wymagane do realizacji zadania, które dodamy do pliku pom.xml.
              <dependency>
                  <groupId>org.hibernate.validator</groupId>
                  <artifactId>hibernate-validator</artifactId>
                  <version>8.0.1.Final</version>
              </dependency>
              <dependency>
                  <groupId>org.springdoc</groupId>
                  <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
                  <version>2.7.0-RC1</version>
              </dependency>
      		
      		<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
              </dependency>
      
      
      
      management.endpoints.web.exposure.include=*
    3. Uruchamiamy ponownie aplikację i sprawdzamy poprawność działania poniższymi poleceniemi :
      • http://localhost:<port>/<context>/v3/api-docs - dostęp do funkcjonalności OpenAPI
      • http://localhost:<port>/<context>/swagger-ui.html - dostęp do funkcjonalności OpenAPI
      • http://localhost:<port>/<context>/v3/api-docs.yaml - dostęp do funkcjonalności OpenAPI
  8. Spring Boot - baza danych H2

    1. Na koniec przygotowanie środowiska testowego z wykorzystaniem bazy danych H2. Tworzymy plik tworzący strukturę w bazie data.sql w katalogu src/main/resources
      DROP TABLE IF EXISTS person;
      CREATE TABLE person ( id serial primary key, fname varchar, lname varchar, city varchar, email varchar(50), tel varchar(50) );
      INSERT INTO person (fname, lname, city, email, tel) VALUES ('Adam', 'Abacki', 'Szczecin','abacki@o2.pl','11222222');  
      INSERT INTO person (fname, lname, city, email, tel) VALUES ('Marek', 'Zazacki', 'Krakow','zazacki@o2.pl','1122244'); 
      INSERT INTO person (fname, lname, city, email, tel) VALUES ('Bogdan', 'Babacki', 'Gdynia','babacki@o2.pl','112451112'); 
      INSERT INTO person (fname, lname, city, email, tel) VALUES ('Witold', 'Dadacki', 'Warszawa','dadacki@o2.pl','11789122'); 
      
    2. Modyfikujemy plik src/main/resources/application.properties
      #Obsluga bazy danyc H2 "in memory"
      spring.datasource.url=jdbc:h2:mem:testdb
      spring.datasource.driverClassName=org.h2.Driver
      spring.datasource.username=sa
      spring.datasource.password=password
      #JPA - implementacja Hibernate
      spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
      spring.jpa.hibernate.ddl-auto=update
      #Konsola Web bazy danych H2
      spring.h2.console.enabled=true
      spring.h2.console.path=/h2-console
      #Port serwera Tomcat
      server.port=8088
      
    3. Dodajemy do projektu sterownik bazy danych H2 w pliku pom.xml
      <dependency>
      	<groupId>com.h2database</groupId>
      	<artifactId>h2</artifactId>
      	<version>2.2.224</version>
      </dependency>
      
    4. Uruchamiamy ponownie aplikację i sprawdzamy poprawność działania poniższymi poleceniemi :
      • http://localhost:<port>/<context>/person - poprawność aplikacji
      • http://localhost:<port>/<context>/h2-console - dostęp do bazy danych ( URL do bazy: jdbc:h2:mem:testdb )
  9. Zaliczenie laboratorium

    1. Przesłanie zrealizowanych w trakcie zajęć przykładowych skryptów w pliku *.war (z kodem źródłowym skryptów)
    2. Przesłanie zrealizowanych w trakcie zajęć ( lub po zajęciach ) poniższych zadań w pliku *.war (z kodem źródłowym skryptów)
      Zrealizowanie pełnych aplikacji realizujących CRUD

      Dodać do wybranej technologii skrypty realizujące funkcjonalność: zapisz, usuń i modyfikuj dane.

      • aplikacja realizująca standardowy wzorzec MVC
      • aplikacji opracowana zgodnie z stylem REST