Страницы

Поиск по вопросам

Показаны сообщения с ярлыком servlet. Показать все сообщения
Показаны сообщения с ярлыком servlet. Показать все сообщения

пятница, 10 января 2020 г.

Переделать веб приложение с сервлетами в RESTful

#java #servlet #rest #веб_служба


Есть приложение, которое соединяется с бд через glassfish ресурсы и все работает.
Используются servlet. Как это можно реализовать с использованием RESTful. Не понимаю
что куда должно передаваться и как. Как REST принимает из формы и возвращает. Все что
я находила было с возвращением статуса, а не  ресурсов. Так же интересно как реализовать
ввод логина и пароля и получать приветствие с именем пользователя, соответствующего
этим логину и паролю в бд. Я совсем плаваю в этом.( 

CRUD операции: 

@Stateless
public class UserLogIn {

@PersistenceContext(unitName = "LEADER")
public EntityManager em;

public UserLogIn(){}



public void add(User user){
    if (!user.getLogin().equals("") || !user.getPassword().equals("") || !user.getUsername().equals("")){
        em.persist(user);
    }
}
public void delete(int id){
    em.remove(getUser(id));
}

public User getUser(int id){
    return em.find(User.class, id);

}
public void update(User user){
    em.merge(user);
}

public List getAll(){
    TypedQuery namedQuery = em.createNamedQuery("User.getAll", User.class);
    return namedQuery.getResultList();
}

}


Сущность: 

@Entity
@Table(name = "annusers")
@NamedQuery(name = "User.getAll", query = "SELECT u FROM User u")
public class User {
@Id
   @SequenceGenerator(name="ad_point_gen",allocationSize=1,sequenceName="ad_points_id_ad_point_seq")
@GeneratedValue(generator="ad_point_gen",strategy=GenerationType.SEQUENCE)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "login", nullable = false)
private String login;
@Column(name = "password", nullable = false)
private String password;
@Column(name="username", nullable = false)
private String username;

public User() {
}

public User(String login, String password, String username) {
    this.login = login;
    this.password = password;
    this.username = username;
}

public String getLogin() {
    return login;
}

public void setLogin(String login) {
    this.login = login;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

@Override
public String toString() {
    return "User{" +
            "login='" + login + '\'' +
            ", password='" + password + '\'' +
            ", username='" + username + '\'' +
            '}';
}

public Integer getId() {
    return id;
}

public void setId(Integer id) {
    this.id = id;
}

public String getUsername() {
    return username;
}

public void setUsername(String username) {
    this.username = username;
}
}


Классы сервлетов для работы с CRUD операциями

@WebServlet("/add")
public class AddAndEditUserServlet extends HttpServlet{
   @EJB
   private UserLogIn userLogIn;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException{
    resp.setContentType("text/html");
    req.setCharacterEncoding("UTF-8");

    if(req.getParameter("edit")!=null){
        int id = Integer.valueOf(req.getParameter("edit"));
        User user = userLogIn.getUser(id);
        req.setAttribute("user",user);
    }

    req.getRequestDispatcher("/add.jsp").forward(req,resp);
}

@Override
protected  void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException{
    resp.setContentType("text/html");
    req.setCharacterEncoding("UTF-8");

    String login = req.getParameter("login");
    String password = req.getParameter("password");
    String username = req.getParameter("username");

    if(!req.getParameter("id").equals("")){
        int id = Integer.valueOf(req.getParameter("id"));
        User user = userLogIn.getUser(id);
        user.setLogin(login);
        user.setPassword(password);
        user.setUsername(username);
        userLogIn.update(user);
    }else {
        userLogIn.add(new User(login, password,username));
    }

    resp.sendRedirect("list");
}

}


package com.mkyong.rest.servlet;



@WebServlet("/delete")
public class DeleteUser extends HttpServlet {

@EJB
private UserLogIn userLogIn;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    if(req.getParameter("id") != null && req.getParameter("id") != ""){
        int id = Integer.valueOf(req.getParameter("id"));
        userLogIn.delete(id);
    }
    resp.sendRedirect("list");
}
}





@WebServlet("/list")
public class UserServlet extends HttpServlet{

@EJB
private UserLogIn userLogIn;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException{
    List allUsers = userLogIn.getAll();
    req.setAttribute("users", allUsers);
    req.getRequestDispatcher("/list.jsp").forward(req,resp);
}

}

    


Ответы

Ответ 1



rest сервисы, как правило нужны для CRUD операций(создание, удаление, редактирование, удаление).например: @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ List allUsers = userLogIn.getAll(); req.setAttribute("users", allUsers); req.getRequestDispatcher("/list.jsp").forward(req,resp); } для запроса /list делает из бд выборку всех пользователей и перенаправляет запрос в list.jsp, где отображается список пользователей. Все что пришло от клиента, находится в объекте класса HttpServletRequest, например: req.getParameter("id") содержит http параметр id Update. Если делать именно рестовский сервис, то надо подключить библиотеку jackson для xml и GSON для json.в методе вместо resp.sendRedirect("list"); возвращаете свой объект, например: @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ List allUsers = userLogIn.getAll(); //.. String json = new Gson().toJson(allUsers); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.getWriter().write(json); }

Ответ 2



Вот так будет выглядеть метод rest-сервиса, который выводит приветствие. Импорты из пакета javax.ws.rs @POST @Path("/test-rest-service") @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN) Response testMethod(String req) { String resp = "Hello " + req; return Response.status(200).entity(resp).encoding("UTF-8").build(); }

четверг, 9 января 2020 г.

Как в gradle скрипте Java проекта настроить создание константы, имеющей разные значения для разных ОС

#java #linux #debian #servlet #gradle


Пишу сервлет на винде, собираю проект через Gradle. Запускать надо будет на Debian.
В проекте используется путь до определённого файла в ОС (драйвер Chrome для Selenium).
На винде и Debian он разный. Как я могу настроить варианты сборки для разных ОС так,
чтобы в них была в Java коде доступна константа, заданная в скрипте сборки и имеющая
разные значения для вариантов сборки?

В андроид это сделать легко, а вот тут сходу не сообразить. Рассмотрю вариант однозначного
программного определения ОС, на коей идёт исполнение и подставление пути до файла в
рантайме.
    


Ответы

Ответ 1



Рассмотрю вариант однозначного программного определения ОС, на коей идёт исполнение и подставление пути до файла в рантайме. Насколько я понимаю для Ваших целей достаточного грубого определения ОС: Windows и не-Windows. Тогда подойдет такой метод: boolean isWindows() { return System.getProperty("os.name").contains("Windows"); } Метод можно использовать при инициализации пути, например: //константы private static final String WINDOWS_PATH = "C:\\driver\\chrome.exe"; private static final String UNIX_PATH = "/usr/bin/chromedriver"; ... //использование pathToExecutable = isWindows() ? WINDOWS_PATH : UNIX_PATH; С помощью Gradle можно записывать путь, например, в файл properties, после чего код будет считывать его в рантайме.

Ответ 2



Если в самом скрипте Gradle то можно воспользоваться следующей конструкцией: import org.apache.tools.ant.taskdefs.condition.Os if (Os.isFamily(Os.FAMILY_WINDOWS) { Тут код } if (Os.isFamily(Os.FAMILY_UNIX) { И тут код }

понедельник, 6 января 2020 г.

Простой сервлет HTTP Status 405 - HTTP method GET is not supported by this URL

#java #servlet


Здравствуйте,код сервлета:

package arver;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by 35717 on 30.03.2016.
 */
public class MainServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
        super.doGet(req, resp);
        PrintWriter out = resp.getWriter();
        out.print("servlet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
        super.doPost(req, resp);
    }
}


Файл web.xml

 
    

        
            MainServlet
            arver.MainServlet
        

        
            MainServlet
            /
        

    


структура папок:



ответ сервера:


  HTTP Status 405 - HTTP method GET is not supported by this URL
  
  type Status report
  
  message HTTP method GET is not supported by this URL
  
  description The specified HTTP method is not allowed for the requested
  resource.
  
  Apache Tomcat/9.0.0.M4

    


Ответы

Ответ 1



Может кому поможет: in your doGet() method, get rid of super.doGet(req, resp); The HttpServlet basically follows the template method pattern where all non-overridden HTTP methods returns a HTTP 405 error "Method not supported". When you override such a method, you should not call super method, because you would otherwise still get the HTTP 405 error. The same story goes on for your doPost() method.

воскресенье, 5 января 2020 г.

Обязательно ли закрывать Statement и ResultSet?

#servlet #jdbc #java


Java сервлет при каждом вызове получает Connection с SQL базой данных (Oracle oracle.jdbc.driver.OracleDriver,
используя DriverManager.getConnection()), затем создает Statement и ResultSet. Все
в локальных переменных. Очевидно (везде пишут об этом), что Statement и ResultSet надо
закрывать.
Вопрос: будет ли утечка ресурсов, если не закрыть ResultSet или Statement?
UPD (в ответ на ответы).
Итак, Oracle XE development, соответственно, версия бесплатная, ограниченная (но
настолько!)
Прежде всего, Statement и ResultSet в нормальном режиме я закрывал, а вот для Exception(s)
ограничился только закрытием Connection в try { ... } finally { ... }
Поэтому для тестирования сначала выбросил Statement и ResultSet close() и в непрерывном
цикле на 77 connection все свалилось, но свалилось по причине ORA-12519. Не Могу ОТКРЫТЬ
новую connection!!! Вот те раз, я же их закрываю! Вернул все close() - то же самое.
Я ведь в непрерывном цикле запросы не гонял, предположить такую подлянку не мог!
Пробую цикл с одним соединением (при закрытии Statement и ResultSet) все работает
(1000000 циклов). Если не закрывать, сначала падает по ResultSet, потом по Statement. 
Почитал про ORA-12519 в сети, увеличил ресурсы (alter system set processes=150 scope=spfile;,
это кому-то помогло) падение отодвинулось до 230 соединений и все.
Потом вычитал, оказалось, дело в том, что ограничено количество соединений в единицу
времени. Сделал 100 в секунду - то идет, то валится. Сейчас идет с 50 в секунду (цикл
100000 после каждой сотни открытых-закрытых sleep(2000)) - YES - прошло. 
Так что, спасибо всем!!!
UPD-2
Искал оптимальную задержку (при повторе попытки открыть новое соединение) оказалось
350 миллисекунд - для 1000 циклов у меня общая задержка в худшем случае 6.3 с. Почему-то
она плавает, нет повторяемости. Для других задержек (от 50мс до 3с) общее время хуже.
Нет ли у кого идей, как правильно организовать подобные замеры?
UPD-3
Oracle - это загадка. Попробовал на работе с "боевой" 11.g Enterprise Edition на сервере.
Выполняется раз в 10 медленнее, поэтому гонял до 10000 циклов. Нет сбоев! Без разницы,
закрываю Statement и ResultSet или нет. Работает и все тут (без всяких sleep()), хотя
драйвер в программе тот же самый от XE Development.
У кого-нибудь есть идеи???  Как вообще правильно программировать взаимодействие с
базой?    


Ответы

Ответ 1



Насчет statement не уверен, но точно знаю, что с resultset будет. Кажется, предел находится на 32к незакрытых resultset, oracle не сможет возвращать результаты выборки. имхо, лучше всегда за собой убирать :)

Ответ 2



Спецификация JDBC рекомендует всегда все закрывать вручную. Однако при этом утверждается, что all Statement objects will be closed when the connection that created them is closed и что closing a Statement object will close and invalidate any instances of ResultSet produced by that Statement object т.е. закрытие соединения должно закрыть все порожденные объекты иерархически. Однако авторы, видимо, решили перестраховаться и защитить вас от случаев, когда "должно" в реализации драйвера перетекает в "мы постараемся" - по техническим причинам либо из-за невнимательности. Видимо, специкация спецификацией, а на практике "всякое может быть" (как бы быдлокодно это не звучало) - так что лучше закрывать самостоятельно. Лично ине кажется, что в драйвере Оракла тут все же будет ОК и с автоматическим закрытием, а вот при переходе на другую БД могут быть сюрпризы.

четверг, 2 января 2020 г.

Почему Servlet имеет такое название?

#java #java_ee #servlet #терминология


Почему "Servlet" имеет такое название? От куда пошло это название? От чего оно происходт?
    


Ответы

Ответ 1



Согласно wiktionary слово Servlet образовано из слов server +‎ applet, где последнее, согласно той же wiktionary образовано из app (сокращение от Application) с приставкой let, обозначающей малый размер В итоге, получается, должно обозначать Маленькая серверная программа

пятница, 27 декабря 2019 г.

Как выдрессировать самца кота?

#java #servlet


у меня стоит Apache Tomcat 8. Я запускаю самый простой сервлет , все норме грузится.
Но проблема  в том, что внося изменения в код классов приходится перезагружать кота.
Отсюда можно сделать выводы, что кот куда-то к себе периодически копирует классы сервлета?
Как сделать, что бы он последнюю версию сервлета обрабатывал?
сам проект небольшой, вот он на гитхабе : нажми на меня
что бы не создавать новую тему. Скажите, зачем прога просит кидать классы(одна из
возможностей работы сервлета) . Ведь она позже их дизассемблирует и добавит свой код.
Проще кидать ей исходники    


Ответы

Ответ 1



Есть одна возможность быстро перезапустить приложение, состоит в том, чтобы отправить соответствующий запрос менеджеру Кота, с указанием приложения. Tomcat Manager App HOW-TO (Reload An Existing Application) Например, что-то вроде этого: http://localhost:8080/manager/text/reload?path=/examples

Ответ 2



Код компилируется в .war или .jar(как правило)-файл. Который собственно и деплоит томкат. А после компиляц3ии твой код становится статичным, поэтому при любых изменениях в коде код перекомпилируется и как следствие заново деплоится. Можно использовать языки сценариев, но про это я не расскажу.

Инициализация одного пула соединений на разные сервлеты Tomcat

#java #spring #servlet #c3p0 #pool


Есть веб-приложение, в котором в файле web.xml определены два сервлета. 
Есть пул соединений c3p0, который, как я понимаю, должен инициализироваться один раз. 

НО! Если обратиться к одному сервлету, а потом к другому, то пул будет инициализирован
дважды и на каждый сервлет окажется свой пул.

Как это исправить, чтобы пул был общим? 

Конфигурация application-context.xml :




    
    



Конфигурация сервлета 1: 




    




Конфигурация сервлета 2:




    
    



Описание бинов для application-context: 

package ru.rusal.mishka.core;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.eclipse.persistence.jpa.PersistenceProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;

import javax.persistence.EntityManagerFactory;
import javax.persistence.ValidationMode;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.util.Properties;

@Configuration

public class ContextConfiguration {

    @Bean
    public DataSource dataSource(
            @Qualifier("systemConfiguration") SystemConfiguration systemConfiguration
    ) throws PropertyVetoException {
        Properties props = new Properties();
        props.setProperty("user", systemConfiguration.getDBConfig().getUser());
        props.setProperty("password", systemConfiguration.getDBConfig().getPassword());

        final ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("oracle.jdbc.OracleDriver");
        dataSource.setJdbcUrl("jdbc:oracle:thin:@localhost:1521:DB");
        dataSource.setInitialPoolSize(5);
        dataSource.setMaxPoolSize(20);
        dataSource.setProperties(props);

        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            @Qualifier("dataSource") DataSource ds
    ) {
        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();

        factoryBean.setPersistenceUnitName("MilliService");
        factoryBean.setDataSource(ds);
        factoryBean.setPersistenceProviderClass(PersistenceProvider.class);
        factoryBean.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
        factoryBean.setValidationMode(ValidationMode.NONE);
        factoryBean.setPackagesToScan("ru.milli.app.persistence");
        factoryBean.getJpaPropertyMap().put("eclipselink.weaving", Boolean.FALSE.toString());
        factoryBean.getJpaPropertyMap().put("eclipselink.allow-zero-id", Boolean.TRUE.toString());

        return factoryBean;
    }

    @Bean
    public JpaTransactionManager transactionManager(
            @Qualifier("entityManagerFactory") EntityManagerFactory factory
    ) throws PropertyVetoException {
        JpaTransactionManager manager = new JpaTransactionManager();
        manager.setEntityManagerFactory(factory);
        return manager;
    }
}


Для сервлетов там обычные rest-сервисы на аннотациях @RestController.

Конфиг web.xml: 




    
    mishka

    
        ru.milli.CleanupContextListener
    

    
        webportal
        org.springframework.web.servlet.DispatcherServlet
    

    
        rest
        org.springframework.web.servlet.DispatcherServlet
    

    
        rest
        /rest/*
    

    
        webportal
        *.html
    

    
        webportal
        /image.jpeg
    

    
        500
        /WEB-INF/jsp/error.jsp
    

    
        404
        /error.html
    

    
        /WEB-INF/jsp/start.jsp
    

    
        
            http://www.springframework.org/tags
            /WEB-INF/tld/spring.tld
        
        
            http://www.springframework.org/tags/form
            /WEB-INF/tld/spring-form.tld
        
    



    


Ответы

Ответ 1



Проблема в том, что вы создаёте 2 DispatcherServlet, и в оба включаете application-context.xml. Согласно документации, A web application can define any number of DispatcherServlets. Each servlet will operate in its own namespace, loading its own application context with mappings, handlers, etc. У каждого DispatcherServlet будет свой контекст Spring, а значит, бины для каждого будут создаваться по отдельности. Тег объединяет текущий конфиг с содержимым импортируемого конфига. Получается, в конфиге каждого сервлета будет объявлен свой пул соединений, и при запуске приложения он создастся два раза. Решить это можно, создав root контекст с бинами, необходимыми всем дочерним контекстам. Делается это при помощи добавления ContextLoaderListener в web.xml: org.springframework.web.context.ContextLoaderListener contextConfigLocation classpath:/path/to/application-context.xml Все бины, определённые в root контексте, будут видны в дочерних (web application) контекстах. При этом дочерний контекст может переопределить бин из родительского контекста (объявив свой бин с таким же id), поэтому включать application-context.xml в конфиг каждого сервлета при такой конфигурации не нужно.

суббота, 7 декабря 2019 г.

Сервлет отрабатывает 2 или более раз

#java #servlet


Сайт написан на чистых сервлетах, никаких фреймворков не используется. При открытии
любой страницы сервлет этой страницы отрабатывает 2 или более раз.

В чем причина?



Маппинг сервлетов в аннотациях. Например, для главной страницы:

Index.java

@WebServlet(name = "index", urlPatterns = {"/index.html"})
public class Index extends AbstractServlet {

    protected static char[] page;

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
        doResponse(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
        doResponse(request, response);
    }

    @Override
    protected void preparePage(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
        StringBuilder stringBuilder = new StringBuilder();

        ... creating page from template ...

        setPage(stringBuilder.toString().toCharArray());
    }
}




AbstractServlet.java

public abstract class AbstractServlet extends HttpServlet {

    protected char[] getPage() throws ServletException, IOException {
        return (char[]) this.getClass().getField("page").get(this.getClass());
    }

    protected void setPage(char[] page) {
        this.getClass().getField("page").set(this.getClass(), page);
    }

    protected void doResponse(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

        PrintWriter pw = response.getWriter();

        if (this.getPage() == null) {
            this.preparePage(request, response);
        }

        pw.write(this.getPage());
    }

    protected abstract void preparePage(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException;
}




Скриншот заголовков первого запроса





Скриншот заголовков второго запроса


    


Ответы

Ответ 1



Клиент указал в сообщении referrer: http://localhost/index.php, это означает что первый запрос инициировал второй (это аутентификация, либо переадресация, или иная ошибка). Теперь видно, что браузер создает второй запрос на /index.html. Но в ответе сервера нет заголовка Redirect /index.html и код ответа 200. Значит в теле ответа есть текст: Либо иные ссылки на /index.html. И судя по Accept идет запрос картинки. Если нет, приложите к вопросу "тело" первого сообщения сервера, разгадка в нем. А сервлет вполне нормально обрабатывает два разных запроса: GET(http://localhost/) GET(http://localhost/index.html)

воскресенье, 1 декабря 2019 г.

Как работает web приложение java?

#java #spring #java_ee #servlet #glassfish


Кто-нибудь может объяснить как работает java web приложение с аннотациями и spring-mvc
совместно с сервером приложений? (В моем случае это Glassfish 4)

Когда мы работаем с консольным приложением java, мы указываем команде java имя скомилированного
класса. Я так понимаю, JRE ищет в указанном классе метод public static void main(String
args[]) и выполняет его. И этот метод main считается точкой входа в программу.


А когда у нас web приложение, у нас имеется какая-то точка входа? Я по началу думал,
что точка входа это web.xml. Но если мы используем spring-mvc и конфигурируем его с
помощью аннотаций, то я так понял web.xml вообще может быть пустым.
Когда глассфишу скармливается war'ник, то я так понимаю в памяти создаются экземпляры
классов которые помечены аннотациями @Service и @Controller, но кто их создает? Я читал,
что это делает спринг, в рамках его IoC, DI и его контейнерной роли, но спринг тоже
кто-то должен запустить? 
По логике, инициализация спринга должна происходить при явном или неявном вызове
его конструкторов в точке входа, но где тогда она? Или его инициализацию производит
сервер приложений? Но каким образом мы указываем ему это сделать?

Вот мой SpringInitializer, я так понимаю основная магия происходит здесь?
Этот код выполняет Glassfish? Как он понимает, что именно этот класс надо выполнить?


import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class SpringInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[] { SpringConfiguration.class };
    }

    @Override
    protected Class[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    } 
}


Я рылся в руководстве Шилдта по Java, а также в Spring in Action, и пару недель пробую
различные запросы по сабжу в гугл. Или в этих книгах не было явных ответов на эти вопросы,
или я их пропустил, или не понял.

Буду признателен за объяснение, или если кто тыкнет носом где читать.
    


Ответы

Ответ 1



Я рылся в руководстве Шилдта по Java, а также в Spring in Action Если вы хотите разобраться с базовыми механизмами работы веб-приложений начинать стоит со спецификации Servlet API (3.0, 3.1). А когда у нас web приложение, у нас имеется какая-то точка входа? Как таковой точки входа нет. Есть контейнер сервлетов, который берет ваш WAR и, если все хорошо, стартует жизненный цикл вашего веб-приложения: устанавливает параметры, оповещает слушателей, пробрасывает запросы в сервлеты, пропускает их через фильтры. По сути, контейнер сервлетов занимается тем, что оперирует объектами, которые можно описать в web.xml, а, начиная с 3й версии спецификации, он должен уметь искать их и без web.xml. ... но кто их создает? Я читал, что это делает спринг, в рамках его IoC, DI и его контейнерной роли, но спринг тоже кто-то должен запустить? Все верно, IoC-контейнер спринга (а именно AnnotationConfigWebApplicationContext в вашем случае) сканирует видимый ему CLASSPATH и создает ваши бины, ориентируясь на аннотации. Осталось понять, кто создает экземпляр этого ApplicationContext и запускает его жизненный цикл (см. ниже). По логике, инициализация спринга должна происходить при явном или неявном вызове его конструкторов в точке входа, но где тогда она? Или его инициализацию производит сервер приложений? Но каким образом мы указываем ему это сделать? Вы очень верно рассуждаете. Начиная со спецификации Servlet API 3.0 появилась возможность не использовать web.xml. Но контейнер сервлетов должен как-то узнать, где находится код, инициализирующий веб-приложение. Тут помогает механизм, известный как Service Provider. Если коротко, он ищет в CLASSPATH для контейнера сервлетов файл-дескриптор META-INF/services/javax.servlet.ServletContainerInitializer, который указывает на реализацию интерфейса ServletContainerInitializer. В случае Spring-а это будет класс SpringServletContainerInitializer. Давайте без стеснения посмотрим на его исходники. На объявлении класса висит аннотация @HandlesTypes: @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer {} По контракту интерфейса ServletContainerInitializer контейнер обязуется передать конкретной реализации в её единственный метод onStartup коллекцию классов, попадающих в перечисленные в аннотации @HandlesTypes. Здесь у нас указан только интерфейс WebApplicationInitializer, поэтому Glassfish передаст все найденные реализации WebApplicationInitializer. Для каждой реализации будет создан экземпляр и вызван метод onStartup: for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } И именно ваш SpringInitializer является такой реализацией. Кроме того SpringInitializer наследует AbstractDispatcherServletInitializer. И вот именно этот AbstractDispatcherServletInitializer и "запускает Spring", создавая WebApplicationContext. Все сошлось. PS. Читайте спецификации и ознакомьтесь с JavaDoc упомянутых в моем ответе классов. В Spring вообще любят подробно описывать все "магические" механизмы в документации ключевых абстрактных классов и интерфейсов.

Ответ 2



Точка входа в виде метода main есть в любом Java приложении. Просто в случае web приложений исполняемой Java программой является контейнер сервлетов / сервер приложений (вот, например, main-метод Tomcat). WEB приложение разворачивается (deploy) внутри контейнера сервлетов. Например, подумайте, если вам требуется задать приложению какие-то аргументы JVM, кому вы их будете передавать? Серверу приложений. Потому что именно он и есть исполняемое приложение, а написанная вами логика существует внутри него и подчиняется спецификации JEE. До версии JEE 6, когда было введено Servlet 3.0 API, эта спецификация требовала помещать конфигурацию вашего приложения в deployment descriptor - web.xml. Начиная с Servlet 3.0 API, этот файл не является обязательным, вместо него приложение может использовать интерфейс ServletContainerInitializer. Когда вы разворачиваете web-приложение, контейнер сервлетов сканирует classpath этого приложения на предмет наличия имплементации указанного интерфейса, а когда находит, то выполняет метод onStartup(), в котором должна выполняться инициализация приложения. Поскольку Spring Web построен на сервлетах, все написанное выше относится к нему самым прямым образом. Конфиг на аннотациях в Spring доступен с версии Servlet 3.0+ API. Реализация интерфейса ServletContainerInitializer находится в модуле spring-web, вы его получаете сразу как подключаете соответствующую зависимость в maven или gradle. Взгляните что делает этот код. Выдержка из документации: Interface which allows a library/runtime to be notified of a web application's startup phase and perform any required programmatic registration of servlets, filters, and listeners in response to it. Implementations of this interface may be annotated with HandlesTypes, in order to receive (at their onStartup(java.util.Set>, javax.servlet.ServletContext) method) the Set of application classes that implement, extend, or have been annotated with the class types specified by the annotation Он получает все классы, которые помечены аннотацией WebApplicationInitializer и инициализирует их. AbstractAnnotationConfigDispatcherServletInitializer, если вы посмотрите его иерархию, как раз является таким классом. Дальше, извините, я просто процитирую ответ в параллеьного сайта, где то же самое расписано пошагово: Servlet context listener (web.xml) approach A web application WAR is being deployed by user. Servlet container (Tomcat) reads web.xml. Servlet context listener ContextLoaderListener is being instantiated (if defined as inside the web.xml) by servlet container. ContextLoaderListener creates new WebApplicationContext with application context XML configuration. Your ROOT context beans are registered and instantiated by BeanFactory inside the application context. DispatcherServlet is being instantiated by servlet container. DispatcherServlet creates its own WebApplicationContext (WEB-INF/{servletName}-servlet.xml by default) with the ROOT context as its parent. Your servlet beans are registered and instantiated by BeanFactory inside the application context. DispatcherServlet registers some default beans in case you did not provide them yourself. Servlet container initializer (non web.xml) approach This one is possible with Servlet 3 features. A web application WAR is being deployed by user. Servlet container searches for classes implementing ServletContainerInitializer via Java's ServiceLoader. Spring's SpringServletContainerInitializer is found and instantiated by servlet container. Spring's initializer reads web application's class-path and searches for WebApplicationInitializer implementations. Your WebApplicationInitializer is found (btw. check its JavaDoc!!!) and instantiated by SpringServletContainerInitializer. Your WebApplicationInitializer creates new ROOT WebApplicationContext with XML or @Configuration based configuration. Your WebApplicationInitializer creates new servlet WebApplicationContext with XML or @Configuration based configuration. Your WebApplicationInitializer creates and registers new DispatcherServlet with the context from previous step. Servlet container finishes the web application initialization and instantiates components which were registered by their class in previous steps (none in my example). Т.о. чтобы запустить приложение на Spring при помощи аннотаций, нужно имплементировать интерфейс WebApplicationInitializer - можно как напрямую, так и унаследоваться от одного из абстрактных классов, которые есть в поставке, и передать туда конфигурационный файл (в вашем примере SpringConfiguration). Дальше Spring получает из него аннтотации @EnableWebMvc, @ComponentScan (это самые основные) и вытягивает остальную конфигурацию. Все вместе это образует ApplicationContext (несмотря на то что написано в единственном числе, их может быть несколько) - описание среды окружения приложения, который как раз и предоставляет возможность обращаться в бинам, переменным и прочим ресурсам приложения с помощью аннотаций.

Ответ 3



Точкой входа и есть сам web.xml к которому обращается контейнер сервлетов при деплое, которым в вашем случае является гласфиш. Когда вы деплоите варку или запускаете проект в своей среде разработки, глазфиш поднимает, все сервлеты и конфигурации из web.xml. 1) web.xml в спринге не остается пустым, просто его работу с сервлетами берет на себя диспетчер сервлетов спринга, но в web.xml мы все равно указываем конфигурации диспетчера. 2)Этот вопрос я несколько раз перечитал и не могу понять его суть. Какой варник и причем здесь аннотации контроллеров и сервисов? Спринг никто запускать не должен, это просто упрощенная версия контейнера бинов, в которой используются аннотации для быстрой и понятной разработки. В памяти создается один бин, который с помощью аннотаций Autowired или Inject можно инжектить не создавая новых экземпляров этого класса, а в случае нескольких реализаций бина, можно выбрать какую именно вы хотите использовать добавив аннотацию Qualifier(name=""). 3)В вашем примере приведен, как я понял, конфигурационный файл, который так же должен быть прописан в вашем web.xml. Если вы используете xml конфигурации, тогда они явно указываются в веб.хмл в приблизительно таком виде: appServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation /WEB-INF/spring/servlet-context.xml 1 Если вы используете классы, тогда как глазфиш понимает, что ему надо дергать это уже зашито в аннотациях самими разработчиками спринга и почему оно так работает, дать ответ я не могу.

вторник, 16 июля 2019 г.

Интернет магазин на Java. Выборка товаров по фильтру.

Допустим у меня в БД есть таблица Product, в которой есть столбцы - атрибуты обєкта: attr1, attr2, attr3. Я хочу сделать так, чтоб пользователь мог фильтровать выборку товаров по атрибутам(фильтр бар, в большинстве интернет магазинов есть):

Значения - val, у меня хранятся в БД - это значения столбцов attr. Я их подтягиваю из БД когда пользователь на страницу заходит. Вот, теперь надо то же сделать с самими атрибутами, то есть, мне надо отобразить названия столбцов. Но вопрос в том, как ? Кроме того, названия столбцов в базе на английском, в мне надо, чтоб на русском отображалось. Захардкодить названия - не самый лучший вариант, ведь при добавлении в базу нового столбца придется менять jsp. Наверняка есть какое то решения, может кто сталкивался с этим или просто знает как лучше сделать, буду рад помощи. Спасибо.


Ответ

Если вытягивать в сервлете названия столбцов, например:
http://www.roseindia.net/jdbc/jdbc-mysql/ColumnName.shtml
https://stackoverflow.com/a/19095179/873481
Если нужно отображать перевод, создайте, например, Map. Там будете хранить пары значений названий столбцов ключ-англ и значение-рус. И, соответственно с нужным значением бд для вывода, будете выводить русское значение столбца по заданному англ.-ключу из бд.

пятница, 12 июля 2019 г.

Когда создается объект HttpSession?

В сервлете есть метод:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println(req.getSession(false)); req.getRequestDispatcher(indexPage).forward(req, resp); }
При использовании apache-tomcat-8.5.15 при первом обращении получается следующий лог:
null null org.apache.catalina.session.StandardSessionFacade@5e21a333
Я всегда думал что существует возможность управлять процессом создания сессии при помощи getSession(true/false). Но выходит что сессия создается вне зависимости от моего желания? Так как же мне обеспечить что бы сессия не создавалась до тех пор, пока я сам не скажу getSession(true)? Хоть я false параметром и выставил, а все равно получил ее создание...
Помогите пожалуйста разобраться что произошло. Спасибо.


Ответ

Скорее всего в вашем приложении использовался JSP. По умолчанию сессия создается при обращении к произвольной JSP странице, если это в явном виде не отключено специальной директивой <%@ page session="false" %>
В остальном вы правы req.getSession(false) не приводит к созданию сессии, если она не была создана до этого.

суббота, 22 июня 2019 г.

Как из фильтра передать управление сервлету?

Есть сервлет myServlet и фильтр MyFilter, который перехватывает обращение к сервлету. После фильтра, как я понял, должен сначала отработать сервлет, после чего он передаёт уже управление в jsp, но не понимаю как ему передать управление?
У меня получается только из фильтра сразу выдать jsp, если я перенаправляю запрос так:
request.getRequestDispatcher("myJsp.jsp").forward(request , response);
В сервлете надо сделать пару важных вычислений.


Ответ

После того как в фильтре выполнили все необходимые действия просто передавайте управление далее по цепочке:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // Выполняем действия...
// Передаем управление далее chain.doFilter(request, response); }
Последним элементом в цепочке и будет сервлет.

вторник, 11 июня 2019 г.

Ошибка вывода данных из servlet в JSP

проблема следующая, пытаюсь отобразить данные переданные как список "List". Но получаю ошибку
java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/servlet/jsp/jstl/core/LoopTagSupport
С чем связанно тоже не могу понять. Вот код servlet:
List list = new ArrayList();
while (resultSets.next()) { Roll roll = new Roll();
roll.setNumbern(resultSets.getInt(1));
roll.setRollnum(resultSets.getInt(2));
roll.setTimer(resultSets.getDate(4));
list.add(roll);
request.setAttribute("name",list); } request.getRequestDispatcher("4kl/test.jsp").forward(request, response);
Вот JSP
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> ${n.Numbern} ${n.Rollnum} ${n.Timer}
Класс Roll
public class Roll implements Serializable { private static final long serialVersionUID = 1L;
private int Numbern;
private int Rollnum;
private Date Timer;
public int getNumbern() { return Numbern; }
public void setNumbern(int numbern) { Numbern = numbern; }
public int getRollnum() { return Rollnum; }
public void setRollnum(int rollnum) { Rollnum = rollnum; }
public Date getTimer() { return Timer; }
public void setTimer(Date timer) { Timer = timer; }
public Roll (){}
}
Pom.xml:
4.0.0
ua.asutp mymvn 0.0.1-SNAPSHOT war
javax.servlet servlet-api 3.0-alpha-1 provided
org.glassfish.main.extras glassfish-embedded-all 3.1.2.2 test
javaee javaee-api 5
jstl jstl 1.2
javax javaee-web-api 6.0 provided
local-oracle ojdbc6
org.hibernate hibernate-core 4.1.11.Final
dom4j dom4j 1.6.1
commons-logging commons-logging 1.2
commons-collections commons-collections 3.2.1
cglib cglib 2.2
javax.transaction jta 1.1

org.apache.maven.plugins maven-compiler-plugin 1.5 1.5
org.apache.maven.plugins maven-war-plugin 2.1.1 false

Возможно я что-то пропустил при вызове c:forEach Помогите пожалуйста разобраться. Может есть способ получше JSTL ? Я только учусь, поэтому мне хотелось бы знать как правильнее выводить данные. Спасибо. Все еще актуально.


Ответ

Не ответ, просто пометка:
request.setAttribute("name",list);
эту строчку вытащите из цикла,добавить то надо всего 1 раз

Ещё один момент по поводу зависимости
javax.servlet servlet-api 3.0-alpha-1 provided
в центральном репозитории я не смог найти такого, попробуйте взять более новую версию, например 4:
javax.servlet javax.servlet-api 4.0.0 provided
эту зависимость тоже стоит обновить. Нет такой версии 5, может быть где-то есть 5,0 но по идее это вам должна была IDE показать
javax javaee-api 7.0 provided

четверг, 21 марта 2019 г.

Java Русские буквы Ubuntu server

Суть проблемы заключается в том что при передаче текстовых данных содержащих русские буквы, на стороне сервлета я вижу "???????? ???? ?????"
А теперь подробнее.
Собираю проект в IDEA, в качестве OS использую Ubuntu Desktop 14.04, запускаю на локальном TOMCAT. Проект запускается, и данные через post запрос передаются на сервлет - все работает отлично.
Собранный проект я копирую на удаленный ubuntu server 14.04 и запускаю на локальном TOMCAT сервере, все работает как надо за исключением одной проблемы - при передаче текстовых данных содержащих русские буквы post запросом на стороне сервера я вижу ???????? ???? ?????
Что я использую:
TOMCAT 8.0.33
java version "1.8.0_91" Java(TM) SE Runtime Environment (build 1.8.0_91-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
вот такой вот JSP
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %>


Вот такой вот Сервлет
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String fio = req.getParameter("fio"); String email = req.getParameter("email");
System.out.println(fio); System.out.println(email); System.out.println(req.getCharacterEncoding()); System.out.println(resp.getCharacterEncoding());
При попытке ввести значение йцуйцу wqe@qwe.ru вот такой вывод
?????? qwe@qwe.ru UTF-8 UTF-8
Кодировка java файлов в IDEA UTF-8
Что я уже пытался делать:
Я пытался поменять\добавить в систему новые локали и теперь это выглядит таком вот образом
locale -a C C.UTF-8 en_AG en_AG.utf8 en_AU.utf8 en_BW.utf8 en_CA.utf8 en_DK.utf8 en_GB.utf8 en_HK.utf8 en_IE.utf8 en_IN en_IN.utf8 en_NG en_NG.utf8 en_NZ.utf8 en_PH.utf8 en_SG.utf8 en_US.utf8 en_ZA.utf8 en_ZM en_ZM.utf8 en_ZW.utf8 POSIX ru_RU ru_RU.cp1251 ru_RU.iso88595 ru_RU.koi8r ru_RU.utf8 ru_UA.utf8
locale LANG=ru_RU.UTF-8 LANGUAGE= LC_CTYPE="ru_RU.UTF-8" LC_NUMERIC="ru_RU.UTF-8" LC_TIME="ru_RU.UTF-8" LC_COLLATE="ru_RU.UTF-8" LC_MONETARY="ru_RU.UTF-8" LC_MESSAGES=POSIX LC_PAPER="ru_RU.UTF-8" LC_NAME="ru_RU.UTF-8" LC_ADDRESS="ru_RU.UTF-8" LC_TELEPHONE="ru_RU.UTF-8" LC_MEASUREMENT="ru_RU.UTF-8" LC_IDENTIFICATION="ru_RU.UTF-8" LC_ALL=
Я пытался установить фильтр
package xxx.filters;
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException;
@WebFilter("/CharEncodingFilter") public class CharEncodingFilter implements Filter {
private static final String FILTERABLE_CONTENT_TYPE="application/x-www-form-urlencoded";
private static final String ENCODING_DEFAULT = "UTF-8";
private static final String ENCODING_INIT_PARAM_NAME = "encoding";
private String encoding;
@Override public void init(FilterConfig config) throws ServletException{ encoding = config.getInitParameter(ENCODING_INIT_PARAM_NAME); if (encoding == null) encoding = ENCODING_DEFAULT; }
@Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException{ String contentType = req.getContentType(); if (contentType != null && contentType.startsWith(FILTERABLE_CONTENT_TYPE)) req.setCharacterEncoding(encoding); resp.setCharacterEncoding(encoding); chain.doFilter(req, resp); }
@Override public void destroy() {
} }
И добавить этот фильтр в web.xml проекта
CharEncodingFilter orion.filters.CharEncodingFilter encoding UTF-8
CharEncodingFilter /*
Я пытался отредактировать server.xml Tomcat server и теперь он выглядит так

Буду благодарен за любую помощь.


Ответ

Проблема оказалась в Tomcat
На стороне ubuntu server, Tomcat воспринимал файлы .class в кодировке ANSI_X3.4-1968
System.out.println("File Encoding = " + System.getProperty("file.encoding"));
File Encoding ANSI_X3.4-1968
На стороне ubuntu desctop, Tomcat воспринимал файлы .class в кодировке UTF-8
System.out.println("File Encoding = " + System.getProperty("file.encoding"));
File Encoding UTF-8
Не понимаю как это возможно
Решение следующие: Необходимо редактировать catalina.sh
sudo nano /opt/tomcat/bin/catalina.sh
И задать кодировку руками. После большого системного комментария добавить строку.
export CATALINA_OPTS="$CATALINA_OPTS -Dfile.encoding=UTF-8"

среда, 20 февраля 2019 г.

Переделать веб приложение с сервлетами в RESTful

Есть приложение, которое соединяется с бд через glassfish ресурсы и все работает. Используются servlet. Как это можно реализовать с использованием RESTful. Не понимаю что куда должно передаваться и как. Как REST принимает из формы и возвращает. Все что я находила было с возвращением статуса, а не ресурсов. Так же интересно как реализовать ввод логина и пароля и получать приветствие с именем пользователя, соответствующего этим логину и паролю в бд. Я совсем плаваю в этом.(
CRUD операции:
@Stateless public class UserLogIn {
@PersistenceContext(unitName = "LEADER") public EntityManager em;
public UserLogIn(){}

public void add(User user){ if (!user.getLogin().equals("") || !user.getPassword().equals("") || !user.getUsername().equals("")){ em.persist(user); } } public void delete(int id){ em.remove(getUser(id)); }
public User getUser(int id){ return em.find(User.class, id);
} public void update(User user){ em.merge(user); }
public List getAll(){ TypedQuery namedQuery = em.createNamedQuery("User.getAll", User.class); return namedQuery.getResultList(); }
}
Сущность:
@Entity @Table(name = "annusers") @NamedQuery(name = "User.getAll", query = "SELECT u FROM User u") public class User { @Id @SequenceGenerator(name="ad_point_gen",allocationSize=1,sequenceName="ad_points_id_ad_point_seq") @GeneratedValue(generator="ad_point_gen",strategy=GenerationType.SEQUENCE) @Column(name = "id", nullable = false) private Integer id; @Column(name = "login", nullable = false) private String login; @Column(name = "password", nullable = false) private String password; @Column(name="username", nullable = false) private String username;
public User() { }
public User(String login, String password, String username) { this.login = login; this.password = password; this.username = username; }
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
@Override public String toString() { return "User{" + "login='" + login + '\'' + ", password='" + password + '\'' + ", username='" + username + '\'' + '}'; }
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; } }
Классы сервлетов для работы с CRUD операциями
@WebServlet("/add") public class AddAndEditUserServlet extends HttpServlet{ @EJB private UserLogIn userLogIn;
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ resp.setContentType("text/html"); req.setCharacterEncoding("UTF-8");
if(req.getParameter("edit")!=null){ int id = Integer.valueOf(req.getParameter("edit")); User user = userLogIn.getUser(id); req.setAttribute("user",user); }
req.getRequestDispatcher("/add.jsp").forward(req,resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ resp.setContentType("text/html"); req.setCharacterEncoding("UTF-8");
String login = req.getParameter("login"); String password = req.getParameter("password"); String username = req.getParameter("username");
if(!req.getParameter("id").equals("")){ int id = Integer.valueOf(req.getParameter("id")); User user = userLogIn.getUser(id); user.setLogin(login); user.setPassword(password); user.setUsername(username); userLogIn.update(user); }else { userLogIn.add(new User(login, password,username)); }
resp.sendRedirect("list"); }
}
package com.mkyong.rest.servlet;

@WebServlet("/delete") public class DeleteUser extends HttpServlet {
@EJB private UserLogIn userLogIn;
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if(req.getParameter("id") != null && req.getParameter("id") != ""){ int id = Integer.valueOf(req.getParameter("id")); userLogIn.delete(id); } resp.sendRedirect("list"); } }


@WebServlet("/list") public class UserServlet extends HttpServlet{
@EJB private UserLogIn userLogIn;
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ List allUsers = userLogIn.getAll(); req.setAttribute("users", allUsers); req.getRequestDispatcher("/list.jsp").forward(req,resp); }
}


Ответ

rest сервисы, как правило нужны для CRUD операций(создание, удаление, редактирование, удаление).например:
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ List allUsers = userLogIn.getAll(); req.setAttribute("users", allUsers); req.getRequestDispatcher("/list.jsp").forward(req,resp); }
для запроса /list делает из бд выборку всех пользователей и перенаправляет запрос в list.jsp, где отображается список пользователей. Все что пришло от клиента, находится в объекте класса HttpServletRequest, например:
req.getParameter("id")
содержит http параметр id
Update.
Если делать именно рестовский сервис, то надо подключить библиотеку jackson для xml и GSON для json.в методе вместо resp.sendRedirect("list"); возвращаете свой объект, например:
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ List allUsers = userLogIn.getAll(); //.. String json = new Gson().toJson(allUsers); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.getWriter().write(json); }

понедельник, 18 февраля 2019 г.

Как в gradle скрипте Java проекта настроить создание константы, имеющей разные значения для разных ОС

Пишу сервлет на винде, собираю проект через Gradle. Запускать надо будет на Debian. В проекте используется путь до определённого файла в ОС (драйвер Chrome для Selenium). На винде и Debian он разный. Как я могу настроить варианты сборки для разных ОС так, чтобы в них была в Java коде доступна константа, заданная в скрипте сборки и имеющая разные значения для вариантов сборки?
В андроид это сделать легко, а вот тут сходу не сообразить. Рассмотрю вариант однозначного программного определения ОС, на коей идёт исполнение и подставление пути до файла в рантайме.


Ответ

Рассмотрю вариант однозначного программного определения ОС, на коей идёт исполнение и подставление пути до файла в рантайме.
Насколько я понимаю для Ваших целей достаточного грубого определения ОС: Windows и не-Windows. Тогда подойдет такой метод:
boolean isWindows() { return System.getProperty("os.name").contains("Windows"); }
Метод можно использовать при инициализации пути, например:
//константы private static final String WINDOWS_PATH = "C:\\driver\\chrome.exe"; private static final String UNIX_PATH = "/usr/bin/chromedriver"; ... //использование pathToExecutable = isWindows() ? WINDOWS_PATH : UNIX_PATH;
С помощью Gradle можно записывать путь, например, в файл properties, после чего код будет считывать его в рантайме.

четверг, 14 февраля 2019 г.

Простой сервлет HTTP Status 405 - HTTP method GET is not supported by this URL

Здравствуйте,код сервлета:
package arver;
import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter;
/** * Created by 35717 on 30.03.2016. */ public class MainServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); PrintWriter out = resp.getWriter(); out.print("servlet"); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); } }
Файл web.xml

MainServlet arver.MainServlet
MainServlet /

структура папок:

ответ сервера:
HTTP Status 405 - HTTP method GET is not supported by this URL type Status report message HTTP method GET is not supported by this URL description The specified HTTP method is not allowed for the requested resource. Apache Tomcat/9.0.0.M4


Ответ

Может кому поможет: in your doGet() method, get rid of
super.doGet(req, resp); The HttpServlet basically follows the template method pattern where all non-overridden HTTP methods returns a HTTP 405 error "Method not supported". When you override such a method, you should not call super method, because you would otherwise still get the HTTP 405 error. The same story goes on for your doPost() method.

вторник, 5 февраля 2019 г.

Обязательно ли закрывать Statement и ResultSet?

Java сервлет при каждом вызове получает Connection с SQL базой данных (Oracle oracle.jdbc.driver.OracleDriver, используя DriverManager.getConnection()), затем создает Statement и ResultSet. Все в локальных переменных. Очевидно (везде пишут об этом), что Statement и ResultSet надо закрывать. Вопрос: будет ли утечка ресурсов, если не закрыть ResultSet или Statement? UPD (в ответ на ответы). Итак, Oracle XE development, соответственно, версия бесплатная, ограниченная (но настолько!) Прежде всего, Statement и ResultSet в нормальном режиме я закрывал, а вот для Exception(s) ограничился только закрытием Connection в try { ... } finally { ... } Поэтому для тестирования сначала выбросил Statement и ResultSet close() и в непрерывном цикле на 77 connection все свалилось, но свалилось по причине ORA-12519. Не Могу ОТКРЫТЬ новую connection!!! Вот те раз, я же их закрываю! Вернул все close() - то же самое. Я ведь в непрерывном цикле запросы не гонял, предположить такую подлянку не мог! Пробую цикл с одним соединением (при закрытии Statement и ResultSet) все работает (1000000 циклов). Если не закрывать, сначала падает по ResultSet, потом по Statement. Почитал про ORA-12519 в сети, увеличил ресурсы (alter system set processes=150 scope=spfile;, это кому-то помогло) падение отодвинулось до 230 соединений и все. Потом вычитал, оказалось, дело в том, что ограничено количество соединений в единицу времени. Сделал 100 в секунду - то идет, то валится. Сейчас идет с 50 в секунду (цикл 100000 после каждой сотни открытых-закрытых sleep(2000)) - YES - прошло. Так что, спасибо всем!!! UPD-2 Искал оптимальную задержку (при повторе попытки открыть новое соединение) оказалось 350 миллисекунд - для 1000 циклов у меня общая задержка в худшем случае 6.3 с. Почему-то она плавает, нет повторяемости. Для других задержек (от 50мс до 3с) общее время хуже. Нет ли у кого идей, как правильно организовать подобные замеры? UPD-3 Oracle - это загадка. Попробовал на работе с "боевой" 11.g Enterprise Edition на сервере. Выполняется раз в 10 медленнее, поэтому гонял до 10000 циклов. Нет сбоев! Без разницы, закрываю Statement и ResultSet или нет. Работает и все тут (без всяких sleep()), хотя драйвер в программе тот же самый от XE Development. У кого-нибудь есть идеи??? Как вообще правильно программировать взаимодействие с базой?


Ответ

Насчет statement не уверен, но точно знаю, что с resultset будет. Кажется, предел находится на 32к незакрытых resultset, oracle не сможет возвращать результаты выборки. имхо, лучше всегда за собой убирать :)

понедельник, 3 декабря 2018 г.

Инициализация одного пула соединений на разные сервлеты Tomcat

Есть веб-приложение, в котором в файле web.xml определены два сервлета. Есть пул соединений c3p0, который, как я понимаю, должен инициализироваться один раз.
НО! Если обратиться к одному сервлету, а потом к другому, то пул будет инициализирован дважды и на каждый сервлет окажется свой пул.
Как это исправить, чтобы пул был общим?
Конфигурация application-context.xml :


Конфигурация сервлета 1:



Конфигурация сервлета 2:


Описание бинов для application-context:
package ru.rusal.mishka.core;
import com.mchange.v2.c3p0.ComboPooledDataSource; import org.eclipse.persistence.jpa.PersistenceProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import javax.persistence.EntityManagerFactory; import javax.persistence.ValidationMode; import javax.sql.DataSource; import java.beans.PropertyVetoException; import java.util.Properties;
@Configuration
public class ContextConfiguration {
@Bean public DataSource dataSource( @Qualifier("systemConfiguration") SystemConfiguration systemConfiguration ) throws PropertyVetoException { Properties props = new Properties(); props.setProperty("user", systemConfiguration.getDBConfig().getUser()); props.setProperty("password", systemConfiguration.getDBConfig().getPassword());
final ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass("oracle.jdbc.OracleDriver"); dataSource.setJdbcUrl("jdbc:oracle:thin:@localhost:1521:DB"); dataSource.setInitialPoolSize(5); dataSource.setMaxPoolSize(20); dataSource.setProperties(props);
return dataSource; }
@Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( @Qualifier("dataSource") DataSource ds ) { LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setPersistenceUnitName("MilliService"); factoryBean.setDataSource(ds); factoryBean.setPersistenceProviderClass(PersistenceProvider.class); factoryBean.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver()); factoryBean.setValidationMode(ValidationMode.NONE); factoryBean.setPackagesToScan("ru.milli.app.persistence"); factoryBean.getJpaPropertyMap().put("eclipselink.weaving", Boolean.FALSE.toString()); factoryBean.getJpaPropertyMap().put("eclipselink.allow-zero-id", Boolean.TRUE.toString());
return factoryBean; }
@Bean public JpaTransactionManager transactionManager( @Qualifier("entityManagerFactory") EntityManagerFactory factory ) throws PropertyVetoException { JpaTransactionManager manager = new JpaTransactionManager(); manager.setEntityManagerFactory(factory); return manager; } }
Для сервлетов там обычные rest-сервисы на аннотациях @RestController.
Конфиг web.xml:

mishka
ru.milli.CleanupContextListener
webportal org.springframework.web.servlet.DispatcherServlet
rest org.springframework.web.servlet.DispatcherServlet
rest /rest/*
webportal *.html
webportal /image.jpeg
500 /WEB-INF/jsp/error.jsp
404 /error.html
/WEB-INF/jsp/start.jsp
http://www.springframework.org/tags /WEB-INF/tld/spring.tld http://www.springframework.org/tags/form /WEB-INF/tld/spring-form.tld


Ответ

Проблема в том, что вы создаёте 2 DispatcherServlet, и в оба включаете application-context.xml. Согласно документации
A web application can define any number of DispatcherServlets. Each servlet will operate in its own namespace, loading its own application context with mappings, handlers, etc.
У каждого DispatcherServlet будет свой контекст Spring, а значит, бины для каждого будут создаваться по отдельности. Тег объединяет текущий конфиг с содержимым импортируемого конфига. Получается, в конфиге каждого сервлета будет объявлен свой пул соединений, и при запуске приложения он создастся два раза.
Решить это можно, создав root контекст с бинами, необходимыми всем дочерним контекстам. Делается это при помощи добавления ContextLoaderListener в web.xml
org.springframework.web.context.ContextLoaderListener contextConfigLocation classpath:/path/to/application-context.xml
Все бины, определённые в root контексте, будут видны в дочерних (web application) контекстах. При этом дочерний контекст может переопределить бин из родительского контекста (объявив свой бин с таким же id), поэтому включать application-context.xml в конфиг каждого сервлета при такой конфигурации не нужно.

четверг, 11 октября 2018 г.

Сервлет отрабатывает 2 или более раз

Сайт написан на чистых сервлетах, никаких фреймворков не используется. При открытии любой страницы сервлет этой страницы отрабатывает 2 или более раз.
В чем причина?

Маппинг сервлетов в аннотациях. Например, для главной страницы:
Index.java
@WebServlet(name = "index", urlPatterns = {"/index.html"}) public class Index extends AbstractServlet {
protected static char[] page;
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doResponse(request, response); }
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doResponse(request, response); }
@Override protected void preparePage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { StringBuilder stringBuilder = new StringBuilder();
... creating page from template ...
setPage(stringBuilder.toString().toCharArray()); } }

AbstractServlet.java
public abstract class AbstractServlet extends HttpServlet {
protected char[] getPage() throws ServletException, IOException { return (char[]) this.getClass().getField("page").get(this.getClass()); }
protected void setPage(char[] page) { this.getClass().getField("page").set(this.getClass(), page); }
protected void doResponse(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter pw = response.getWriter();
if (this.getPage() == null) { this.preparePage(request, response); }
pw.write(this.getPage()); }
protected abstract void preparePage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; }

Скриншот заголовков первого запроса


Скриншот заголовков второго запроса


Ответ

Клиент указал в сообщении referrer: http://localhost/index.php, это означает что первый запрос инициировал второй (это аутентификация, либо переадресация, или иная ошибка).
Теперь видно, что браузер создает второй запрос на /index.html. Но в ответе сервера нет заголовка Redirect /index.html и код ответа 200. Значит в теле ответа есть текст:

Либо иные ссылки на /index.html. И судя по Accept идет запрос картинки.
Если нет, приложите к вопросу "тело" первого сообщения сервера, разгадка в нем.
А сервлет вполне нормально обрабатывает два разных запроса:
GET(http://localhost/) GET(http://localhost/index.html)