728x90
반응형

이 글은 Thymeleaf 3.0 버전 기준으로 작성되었습니다.

 

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

 

Tutorial: Using Thymeleaf

1 Introducing Thymeleaf 1.1 What is Thymeleaf? Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text. The main goal of Thymeleaf is to provide a

www.thymeleaf.org

 

 

 

 

현재 회사 프로젝트에서 Thymeleaf 로 JSP 를 대신해 화면을 구현하고 있습니다.

 

Tiles 처럼 layout 을 만들어서 진행하려고 찾고 진행해보려고 찾아보니 thymeleaf-layout-dialect 를 사용하여 구현하는 글이 많았습니다.

 

gradle 기준 implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' 의존성 주입을 받아 진행하였습니다.

 

그런데 아무리 해봐도 실행되지 않았습니다.

 

찾아보니 3.0 버전에선 사용이 불가능하다고 하고. 그래도 기존에 돌아가던 코드를 깃헙에서 받아 실행해보았지만 역시 안되네요.

 

그래서 그냥 Thymeleaf 3.0 문서를 보고 구현해보았습니다.

 

 

 

 

 

layout 구현부 입니다.

 

th:fragment="layout(title, content)" 이 부분이 상속을 받을수 있게 해줍니다.

 

fragment 를 생성하면서 함수 인자로 title 과 content 를 받고 있습니다.

 

<title th:replace="${title}">Layout Title</title> 변수를 출력하는 코드로 동적 title 사용이 가능합니다.

 

<div th:replace="${content}"></div> 동적으로 content 를 출력하는 코드 입니다.

 

<!DOCTYPE html>
<html th:fragment="layout(title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:replace="${title}">Layout Title</title>
</head>

<body>

    <h1>Layout H1</h1>

    <div th:replace="${content}">

        <p>Layout content</p>

    </div>

    <footer>
        Layout footer
    </footer>

</body>
</html>

 

 

 

 

 

 

content 에 들어갈 html 입니다.

 

html 태그에 th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}" 이런식으로 layoutFile 파일을 선택하고 :: 구분자를 넣어주고 layout() 함수를 호출하는 방식입니다.

 

~{title} title 태그를 주입받습니다.

 

~{section} section 태그를 주입받습니다.

 

두개다 역시 head 나 body 어떤 태그를 사용할 수 있습니다.

 

<!DOCTYPE html>
<html th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}">
<head>
    <title>Page Title</title>
</head>
<body>

<section>

    <p>Page content</p>

    <div>Included on page</div>

</section>
</body>

 

 

 

 

 

 

이대로 실행하면 layout 이 정해진 html 의 content 부분에 section 태그 내용들이 보일것 입니다.

 

툴에 따라 다르지만 IntelliJ 에서 빨간 밑줄이 나오지만 문제없이 돌아갑니다.

 

eclipse 는 빨간줄이 나오지는 않습니다.

 

이 layout 구현을 하려고 Thymeleaf 2.x 대로 내려가려고 했지만 더 가볍게 사용이 가능하니 Thymeleaf 3.x 를 그대로 사용하는걸 추천드립니다.

 

728x90
반응형
728x90
반응형

 

프로젝트를 진행하는데 기존 시스템이 있는곳이라 DB를 2개 이상 사용해야된다고 합니다.

 

이제 Spring Boot 2.x 를 공부겸 진행하고 있는데 매번 난관이군요.

 

늘 생각하는거지만 실제론 쉬운 코드인데 엄청 어렵게 설명되어있는 글들이 많네요.

 

일단 제가 작성한대로 정리해보겠습니다.

 

 

 

 

 

 

Packacge 구조

다중 DB를 사용할 때에는 package 로 구분하게 됩니다.

 

customer 와 user 로 분류하여 진행하도록 하겠습니다.

-com
    -project
        - customer
            - config
                - CustomerDataSourceConfig.java
            - domain
                - Customer.java
            - repository
                - CustomerRepository.java
        -user
            - config
                - UserDataSourceConfig.java
            - domain
                - User.java
            - repository
                - UserRepository.java
        DataSourceProperties.java
 

 

 

application.yml

Oracle을 사용하기에 JDBC jar를 등록하여 사용했습니다.

 

https://gigas-blog.tistory.com/112

 

[Spring] Gradle 외부 Jar 등록

Spring Boot Project 를 만들면 Maven 이나 Gradle이 기본적으로 설치되어있습니다. 다양한 library 을 쉽고 간편하게 가져와서 쓸수 있지만 외부와의 통신을 허용하지 않는 개발 환경이 있기도 합니다. 이럴때에..

gigas-blog.tistory.com

SpringBoot 2.x 부터 기본 jdbc 로 hikari를 사용하고 있기 때문에 url 이 아닌 jdbc-url로 수정해 주어야 합니다.

 

url 과 driver-class-name, username, password 는 db에 맞는 정보로 수정해야 합니다.

 

customer 와 user 이렇게 2개로 분리하였습니다.

server:
  port: 9099


spring:
  datasource:
    hikari:
      customer:
        jdbc-url: jdbc:oracle:thin:@localhost:1521:xe
        username: -
        password: -
        driver-class-name: oracle.jdbc.OracleDriver
        
      user:
        jdbc-url: jdbc:oracle:thin:@-:-
        username: -
        password: -
        driver-class-name: oracle.jdbc.OracleDriver
 

 

 

DataSourceProperties.java

공통적인 DataSource 부분은 한곳에 정의하였습니다.

 

@Bean 으로 등록할 때 이름을 지정해주어 사용하려고 name을 등록해주었습니다.

 

@Qualifier 로 객체를 선택할 수 있도록 지정해 줍니다.

 

@Primary 주가 되는 DataSource를 지정해 줍니다.

 

@ConfigurationProperties yml이나 properties 속성을 지정해 줍니다.

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
@EnableConfigurationProperties
public class DataSourceProperties {
    
    @Bean(name = "customerDataSource")
    @Qualifier("customerDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.hikari.customer")
    public DataSource customerDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    
    @Bean(name = "userDataSource")
    @Qualifier("userDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.hikari.user")
    public DataSource userDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
}

 

 

Customer.java

User.java 와 동일합니다.

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Data;

@Entity
@Data
@Table(name = "CUSTOMER")
public class Customer {

  @Id
  @Column(name = "컬럼명")
  private  String column1;

  @Column(name = "컬럼명")
  private String column2;

}
 

 

 

CustomerRepository.java

CustomerRepository 입니다.

import org.springframework.data.jpa.repository.JpaRepository;

import com.project.customer.domain.Customer;

public interface CustomerRepository extends JpaRepository<Customer, Long>{

}

 

 

 

CustomerDataSourceConfig.java

주가 되는 Customer 입니다.

 

User와는 @Primary 차이만 있을뿐 기본적인 설정은 같습니다.

 

@EnableJpaRepositories의 basePackages 는 repository 경로를 설정해주면 됩니다.

 

customerEntityManagerFactory 의 packages 는 domain 경로를 설정해주면 됩니다.

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "customerEntityManagerFactory", 
        transactionManagerRef = "customerTransactionManager", 
        basePackages = { "com.project.customer.repository" })
public class CustomerDataSourceConfig {
    
    @Autowired
    @Qualifier("customerDataSource")
    private DataSource customerDataSource;
    
    @Primary
    @Bean(name = "customerEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean customerEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(customerDataSource)
                .packages("com.project.customer.domain")
                .persistenceUnit("customer")
                .build();
    }
    
    @Primary
    @Bean("customerTransactionManager")
    public PlatformTransactionManager customerTransactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(customerEntityManagerFactory(builder).getObject());
    }

}

 

 

 

User.java

User.java 를 생성하였습니다.

 

테이블명과 컬럼명, 변수명은 DB에 맞게 맞춰주면 됩니다.

 

필요에 맞게  @GeneratedValue(strategy=GenerationType.AUTO) 를 생성해주시면 됩니다.

import javax.persistence.Column;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Data;

@Entity
@Data
@Table(name = "USER")
public class User {

  @Id
  @Column(name = "컬럼명")
  private  String column1;

  @Column(name = "컬럼명")
  private String column2;

}

 

 

UserRepository.java

UserRepository 입니다.

import org.springframework.data.jpa.repository.JpaRepository;

import com.project.user.domain.User;

public interface UserRepository extends JpaRepository<User, String>{

}

 

 

UserDataSourceConfig.java

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "userEntityManagerFactory", 
        transactionManagerRef = "userTransactionManager", 
        basePackages = { "com.project.user.repository" })
public class UserDataSourceConfig {

    @Autowired
    @Qualifier("userDataSource")
    private DataSource userDataSource;

    @Bean(name = "userEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean userEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(userDataSource)
                .packages("com.project.user.domain")
                .persistenceUnit("user")
                .build();
    }

    @Bean("userTransactionManager")
    public PlatformTransactionManager userTransactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(userEntityManagerFactory(builder).getObject());
    }
}

 

 

 

 

@Primary 설정으로 @Transactional 처리의 기준이 됩니다.

 

기본 설정이 아닌 DB 의 Transaction을 처리 하려면 @Transactional(value = "userTransactionManager") 이런식으로 사용하면 됩니다.

 

간단하게 test를 해보았습니다.

 

각각의 package의 기준으로 분리된 db 를 조회해서 정상적으로 출력이 된다면 잘 적용된것을 알 수 있습니다.

 

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.project.customer.repository.CustomerRepository;
import com.project.user.repository.UserRepository;

import lombok.extern.slf4j.Slf4j;

@RunWith(SpringRunner.class)
@Slf4j
@SpringBootTest
public class SpringBootMultipleJpaApplicationTests {
    
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private CustomerRepository customerRepository;

    @Test
    public void userTest() {
        userRepository.findAll()
            .stream()
            .map(i -> i.toString())
            .forEach(log::info);
    }

    @Test
    public void custoemrTest(){
        customerRepository.findAll()
            .stream()
            .map(i -> i.toString())
            .forEach(log::info);
    }
}
 

 

 

 

부가적으로 설명해야할 부분이 많지만 기본적인 지식을 갖고 있는 분을 기준으로 작성했으며 다른 속성들은 문의 주시면 감사하겠습니다.

 

728x90
반응형
728x90
반응형

 

회사에서 개발을 진행하는데 비밀번호 인증번호를 메일로 전송해야하는 상황이 생겼습니다.

 

Sprint Boot 로 개발을 해보려고 찾아보던중 간단하게 Mail을 전송하는 방법을 정리해보려고 합니다.

 

너무 많은 정보들 중에 꼭 필요한 부분만 처리해야하지만 복잡하게 설명이 되어있어 간략하게 진행해본뒤에 작성하는 글 입니다.

 

개발 환경은 Spring Boot 2.x + Gradle 입니다.

 

 

 

 

선행해야되는 부분은 발송설정을 Gmail 등록할땐 보안 수준이 낮은 앱의 액세서를 꼭 허용해야 합니다.

 

허용하지 않고 진행한다면 아래와같은 메일을 받으실 수 있습니다.

활동확인으로 보안수준이 낮은 앱 차단이 되고 있는지 알수 있습니다.

 

 

Google에서 액세스를 막고 있기 때문에 아래의 URL로 접속해서 허용을 해주시면 됩니다.

 

https://myaccount.google.com/lesssecureapps

 

로그인 - Google 계정

하나의 계정으로 모든 Google 서비스를 Google 계정으로 로그인

accounts.google.com

 

 

build.gradle 에 mail 관련 의존성 주입

/// Mail
implementation 'org.springframework.boot:spring-boot-starter-mail'

 

 

 

application.yml 에서 mail 설정

Gmail 기준으로 작성해보았습니다.

 

host 는 고정값이며, port 는 465 or 587 로 사용가능합니다.

 

id에 @gmail.com 생략 가능하고 password 도 입력해주시면 됩니다.

 

Gmail 인증관련의 TLS 설정을 해줍니다.

# ===============================

# = Email Config

# ===============================

spring:

    mail:

        # Gmail smtp

        host: smtp.gmail.com

 

        # 465 또는 587

        port: 587

 

        # id

        username: 아이디

 

        # password

        password: 비밀번호

 

        # TLS

        properties:

            mail:

                smtp:

                    auth: true

                    starttls:

                        enable: true

cs

 

 

 

 

Java 코드를 작성

EmailService 라는 class 를 생성해주었습니다.

 

@Service 어노테이션을 통해 Bean으로 등록해줍니다.

 

JavaMailSender 과 TemplateEngine 을 생성자로 의존성 주입을 받습니다.

 

받는 메일주소와 제목, 내용을 파라미터로 받고 MimeMessagePreparator 와 MimeMessageHelper 를 구현합니다.

 

build() 함수에서 Thymeleaf 파일경로를 잡아주고 Thymeleaf 에서 사용할 변수를 설정해줄 수 있습니다.

 

지금은 text 라는 변수만 생성해 주었으며 messageHelper.setText() 로 설정한 content 를 넣어주고 두번째 인자에선 html로 사용하겠다는 설정을 해주었습니다.

 

기본값은 text이기 때문에 <html> ... <html> 형태로 받게 되니 꼭 true 값을 넣어줍니다.

import org.springframework.mail.javamail.JavaMailSender;

import org.springframework.mail.javamail.MimeMessageHelper;

import org.springframework.mail.javamail.MimeMessagePreparator;

import org.springframework.stereotype.Service;

import org.thymeleaf.TemplateEngine;

import org.thymeleaf.context.Context;

 

@Service

public class EmailService {

 

    // Java 메일

    private JavaMailSender javaMailSender;

 

    // Thymeleaf Template 사용

    private TemplateEngine templateEngine;

 

    public EmailService(JavaMailSender javaMailSender, TemplateEngine templateEngine) {

        this.javaMailSender = javaMailSender;

        this.templateEngine = templateEngine;

    }

 

    public void sendSimpleMessage(String to, String subject, String text) {

        // Thymeleaf 사용

        MimeMessagePreparator message = mimeMessage -> {

            String content = build(text);

 

            MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage);

            messageHelper.setTo(to);

            messageHelper.setSubject(subject);

            messageHelper.setText(content, true);

        };

        javaMailSender.send(message);

    }

 

    /*

        Thymeleaf 셋팅

     */

    private String build(String text) {

        Context context = new Context();

        context.setVariable("text", text);

        return templateEngine.process("mail-template", context);

    }

}

Colored by Color Scripter

cs

 

 

 

 

메일을 전송했을때 보여주는 html 파일을 생성

Java에서 등록한 변수를 바로 사용할 수 있습니다.

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

<head>

    <meta charset="UTF-8">

    <title>Mail Template</title>

</head>

<body>

 

    <span th:text="${text}"></span>

</body>

</html>

Colored by Color Scripter

cs

 

 

 

테스트 코드 작성

test package 에서 EmailSendTest 클래스를 생성하고 테스트를 진행해보면 됩니다.

 

테스트가 정상적으로 진행이 되면 메일을 확인할 수 있습니다.

import com.kcfinance.service.EmailService;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

 

@RunWith(SpringRunner.class)

@SpringBootTest

public class EmailSendTest {

 

    // 이메일 발송 테스트

    @Autowired

    private EmailService emailService;

 

 

    @Test

    public void sendEmail() {

        String to = "받는주소";

        String subject = "메일 테스트";

        String text = "메일 내용";

 

        emailService.sendSimpleMessage(to, subject, text);

    }

}

 

Colored by Color Scripter

cs

 

728x90
반응형
728x90
반응형

 

SQL Dialects

항상 데이터베이스 hibernate.dialect의 올바른 org.hibernate.dialect.Dialect서브 클래스로 특성을 설정하세요.

 

dialect를 지정하면, Hibernate는 위에 열거 된 다른 프로퍼티들 중 일부에 적절한 기본값을 사용할 것입니다.

 

즉, 수동으로 지정하지 않아도됩니다.

 

Hibernate SQL Dialects ( hibernate.dialect)

DB2 org.hibernate.dialect.DB2Dialect
DB2 AS/400 org.hibernate.dialect.DB2400Dialect
DB2 OS390 org.hibernate.dialect.DB2390Dialect
PostgreSQL org.hibernate.dialect.PostgreSQLDialect
MySQL5 org.hibernate.dialect.MySQL5Dialect
MySQL5 with InnoDB org.hibernate.dialect.MySQL5InnoDBDialect
MySQL with MyISAM org.hibernate.dialect.MySQLMyISAMDialect
Oracle (any version) org.hibernate.dialect.OracleDialect
Oracle 9i org.hibernate.dialect.Oracle9iDialect
Oracle 10g org.hibernate.dialect.Oracle10gDialect
Oracle 11g org.hibernate.dialect.Oracle10gDialect
Sybase org.hibernate.dialect.SybaseASE15Dialect
Sybase Anywhere org.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Server 2000 org.hibernate.dialect.SQLServerDialect
Microsoft SQL Server 2005 org.hibernate.dialect.SQLServer2005Dialect
Microsoft SQL Server 2008 org.hibernate.dialect.SQLServer2008Dialect
SAP DB org.hibernate.dialect.SAPDBDialect
Informix org.hibernate.dialect.InformixDialect
HypersonicSQL org.hibernate.dialect.HSQLDialect
H2 Database org.hibernate.dialect.H2Dialect
Ingres org.hibernate.dialect.IngresDialect
Progress org.hibernate.dialect.ProgressDialect
Mckoi SQL org.hibernate.dialect.MckoiDialect
Interbase org.hibernate.dialect.InterbaseDialect
Pointbase org.hibernate.dialect.PointbaseDialect
FrontBase org.hibernate.dialect.FrontbaseDialect
Firebird org.hibernate.dialect.FirebirdDialect

출처 : Hibernate

 

728x90
반응형
728x90
반응형

 

이전에 Spring Boot + Gradle 프로젝트를 Jar로 배포하는 방법을 알아보았습니다.

 

하지만 Spring Boot 에선 Jar로 배포시 JSP 사용이 불가하고 webapp 폴더를 무시한다고 합니다.

 

그렇다면 국내 솔루션 업체들이 license 를 제공하는 방식이 WEB-INF 안에 파일유무를 체크하는데 

 

Jar 로 배포하면 사용하지 못한다는게 현실입니다.

 

https://gigas-blog.tistory.com/114

 

[Spring] Spring Boot 2.0 Gradle Jar 배포

Spring Boot 의 강점은 내장 서버가 있어 실행 가능한 Jar 파일을 만들어 배포가 쉽습니다. Spring Boot의 많은 의존성 Library 와 사용자가 추가한 Library 를 하나의 Jar파일로 패키징 해준다는게 정말 대단한것..

gigas-blog.tistory.com

 

 

 

 

War는 별도의 내장 서버를 사용하지 않고 외장 서버에 배포할 수 있습니다.

 

예전부터 웹 서버에 반영을 해본분들은 War 배포를 많이 아시겠지만 요즘 새로 시작하시는분들은 낮설 수 있어서 알아보려고 합니다.

 

 

 

 

 

Sprint Boot + Gradle 프로젝트를 SpringBootTest 이름으로 생성했습니다.

 

생성할때 Packaging 을 War로 선택하시면 SevletInitializer.java 라는 파일이 생성됩니다.

 

SpringBoot 에서는 web.xml 을 사용하지 않기 때문에 이곳에서 설정이 가능합니다.

 

그리고 War 로 배포할때엔 여기서 main을 구축해야만 합니다.

 

 

 

 

 

기존처럼 SpringBootTestApplication.java 를 사용하지 않기 때문에 전체를 주석처리 하거나 삭제합니다.

 

개발은 내장톰켓을 사용하시는 분들은 주석처리하는게 편하실겁니다.

//@SpringBootApplication
//public class SpringBootTestApplication {

//    public static void main(String[] args) {
//        SpringApplication.run(SpringBootTestApplication.class, args);
//    }

//}

 

 

 

 

ServletInitializer class 에 @SpringBootApplication 어노테이션을 추가합니다.

 

configure 함수의 SpringBootTestApplication.class 에서 SevletInitializer.class 로 수정하고, main 함수도 정의해주겠습니다.

 

Java 에서의 설정은 이것으로 마무리 하고 Gradle 설정을 해보겠습니다.

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

// War
@SpringBootApplication
public class ServletInitializer extends SpringBootServletInitializer {

    // DevTool
//    @Override
//    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//        return application.sources(SpringBootTestApplication.class);
//    }

    // War Packaging
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(ServletInitializer.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(ServletInitializer.class, args);
    }

}

 

 

 

 

build.gradle 파일을 열어서 War plugin을 추가해줍니다.

plugins {
    id 'war'
}

// or

apply plugin: 'war'

 

 

 

 

build.gradle 에서 War 생성관련 설정을 할 수 있습니다.

 

Name 과 Version, File Name 등을 설정할 수 있으며 따로 설정이 없다면 프로젝트명 + Version 이 붙어서 생성됩니다.

bootWar {
    archiveBaseName = '-'
    archiveFileName = '-.war'
    archiveVersion = "0.0.0"
}

 

 

 

 

그리고 외부 톰캣을 사용한다는 providedRuntime 을 설정해주어야합니다.

 

기존에 사용하던 runtimeOnly 'org.springframework.boot:spring-boot-devtools' 이부분은 주석처리하거나 제거해주면 됩니다.

providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'

 

 

 

 

 

준비는 끝났으며 War 를 만들어줍니다.

 

CLI 터미널에서 명령어로 생성해보겠습니다.

 

만들어준 SpringBootTest 경로로 들어갑니다.

 

Spring Boot 를 War로 생성하기 때문에 bootWar 명령어롤 통해 War 를 생성합니다.

 

 

 

 

 

 

빌드가 성공하면 프로젝트 경로로 들어가서 확인할 수 있습니다.

 

build > libs 안에 War 파일이 생성됨을 알 수 있습니다.

 

 

 

 

 

 

 

IntelliJ 에서는 Gradle 탭에서 간단하게 생성이 가능합니다.

 

Tasks > build 안에 실행가능한 bootWar 스크립트를 더블클릭하면 됩니다.

 

 

 

 

 

 

 

정상적으로 실행이 되면 아래와 같은 문구가 보여질 것입니다.

 

 

 

 

 

 

War 파일을 생성했으니 실행하는 방법을 알아보겠습니다.

 

서버와 마찬가지로 로컬에도 톰캣을 돌려서 실행할 수 있습니다.

 

저는 Tomcat 9 를 설치한 다음 webapps 폴더 안에 .war 를 넣어주었습니다.

 

 

 

 

 

Deploy 절차를 진행하기 위해 Tomcat을 새로 시작하겠습니다.

 

터미널 에서 tomcat 이 설치된 경로에서 bin파일로 진입합니다.

 

많은 실행파일이 있지만 shutdown.sh 와 startup.sh 파일을 사용해서 tomcat 서버를 종료하거나 실행하면됩니다.

 

tomcat 종료 ./shutdown.sh

 

tomcat 실행 ./startup.sh

 

 

 

 

 

정상적으로 오류없이 서버가 동작하였습니다.

 

 

 

 

 

 

Tomcat 을 실행하면 war 파일을 Deploy 하여 폴더가 만들어지게 됩니다.

 

 

 

 

 

 

잘 로드 되는지 확인해보겠습니다.

 

war 파일명으로 폴더가 생성되기때문에 조금 긴 접속 경로가 설정되었네요.

 

http://localhost:8080/SpringBootTest-0.0.1-SNAPSHOT/

 

 

 

 

 

 

페이지는 따로 만들지 않았기 때문에 아래와 같이 나온다면 정상적으로 War 배포가 된것을 확인할 수 있습니다.

 

 

 

 

War로 배포했을때 ContextRoot 경로때문에 정보가 잘 나오지 않을 수 있습니다.

 

다음글에선 ROOT 설정에 대해서 알아보겠습니다.

 

728x90
반응형
728x90
반응형

 

IntelliJ 에서는 다양한 plugin을 지원하고 있습니다.

 

모든 plugin 이 설치가 되있다면 엄청 무거운 프로그램이 되기 때문에 필요한 plugin 만설치하도록 권장하고 있습니다.

 

Spring Boot 개발하면서  Lombok plugin 을 많이 사용하는데 자세한 정보를 찾아보는걸 권장드립니다.

 

Lombok plugin 이 설치되어있지 않다면 프로젝트에 빨간줄이 그어집니다.

 

빌드할때에는 문제가 되지 않지만 자동완성등 전체적인 기능을 지원받기위해서  lombok을 설치해보겠습니다.

 

 

 

 

 

프로그램 설명은 MAC 기준으로 작성되었습니다.

 

IntelliJ IDEA > Preferences 메뉴로 이동합니다.

 

 

 

Plugins 탭으로 이동합니다.

 

검색 필드에 lombok을 입력합니다.

 

 

아래와 같이 자동완성이 되어지는데 첫번째 Lombok을 클릭합니다.

 

 

Lombok 설명이 나와있고 우측 Install 버특을 클릭합니다.

 

 

내용에 동의합니다.

 

 

 

설치는 오래걸리지 않습니다.

 

Restart IDE 버튼을 클릭합니다.

 

 

Restart 를 다시 클릭합니다.

 

 

설치가 완료되고 바로 적용되는건 아닙니다.

 

남은 설정을 위해 IntelliJ IDEA > Preferences 메뉴로 이동합니다.

 

Build, Execution, Deployment > Compiler > Annotation Processors 메뉴로 이동합니다.

 

우측의 Enable annotaition processing 체크박스를 체크 하고 OK 버튼을 클릭하면 마무리가 됩니다.

 

 

getter setter 빨간 에러는 사라지고 어노테이션 자동완성도 잘되는것을 알 수 있습니다.

728x90
반응형
728x90
반응형

 

Spring Boot 의 강점은 내장 서버가 있어 실행 가능한 Jar 파일을 만들어 배포가 쉽습니다.

 

Spring Boot의 많은 의존성 Library 와 사용자가 추가한 Library 를 하나의 Jar파일로 패키징 해준다는게 정말 대단한것 같습니다.

 

Spring Boot + Gradle 프로젝트를 생성하면 기본 Jar 를 배포할 수 있도록 설정이 되어있습니다.

 

 

 

 

Jar 파일로 생성할 때에는 로컬 환경과 동일하며 따로 설정이 필요없다는 장점이 있습니다.

 

SpringBootTest 라는 프로젝트를 생성했을때 만들어지는 프로젝트명Application 에서 내장톰켓을 사용하는데 이대로 Jar 로 빌드하면 됩니다.

 

package com.gigas;



import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;



@SpringBootApplication

public class SpringBootTestApplication {



    public static void main(String[] args) {

        SpringApplication.run(SpringBootTestApplication.class, args);

    }

}

 

 

 

 

 

build.gradle 에서 jar 생성관련 설정을 할 수 있습니다.

 

Name 과 Version, File Name 등을 설정할 수 있으며 따로 설정이 없다면 프로젝트명 + Version 이 붙어서 생성됩니다.

 

bootJar {

    archiveBaseName = '-'

    archiveFileName = '-.jar'

    archiveVersion = "0.0.0"

}

 

 

 

 

준비는 끝났으며 Jar 를 만들어줍니다.

 

CLI 터미널에서 명령어로 생성해보겠습니다.

 

만들어준 SpringBootTest 경로로 들어갑니다.

 

Spring Boot 를 Jar로 생성하기 때문에 bootJar 명령어롤 통해 Jar 를 생성합니다.

 

 

 

 

빌드가 성공하면 프로젝트 경로로 들어가서 확인할 수 있습니다.

 

build > libs 안에 jar 파일이 생성됨을 알 수 있습니다.

 

 

 

IntelliJ 에서는 Gradle 탭에서 간단하게 생성이 가능합니다.

 

Tasks > build 안에 실행가능한 bootJar 스크립트를 더블클릭하면 됩니다.

 

 

 

정상적으로 실행이 되면 아래와 같은 문구가 보여질 것입니다.

 

 

 

 

실행가능한 Jar 파일을 생성했으니 실행하는 방법을 알아보겠습니다.

 

간단합니다. java -jar JarName.jar 명령어로 실행 할 수 있습니다.

 

그러면 아래와 같이 같은 로컬환경이지만 실행가능한 Jar 로 배포를 한것입니다.

 

 

http://localhost:8080 으로 접속했습니다.

 

port 를 따로 지정했을시 port만 바꿔서 접속 가능합니다. 

 

개인 서버가 있으시다면 업로드 후에 동일하게 명령어를 입력하면 정상적으로 실행되는것을 확인할 수 있습니다.

 

실행하기전에 로컬에 띄워져있는 서버와 Port 가 충돌날 가능성이 높으니 주의하시기 바랍니다.

 

728x90
반응형
728x90
반응형

 

이전에 작성했던 Custom Error Page 에 대한 글 url 입니다.

 

https://gigas-blog.tistory.com/110

 

[Spring] Spring Boot 2.0 Error Custom Page

Spring Boot 2.0 기준으로 작성되었습니다. 홈페이지를 만들었는데 소스코드가 보여지는 에러페이지가 보여진다면 사용자 입장에서 잘 만들어진 홈페이지라고 생각하지 않을겁니다. 이런 디테일함 하나가 좋은 사..

gigas-blog.tistory.com

 

 

 

Spring reference 를 찾아보니 SpringBoot 에서 제공하는 쉬운 방식이 있었습니다.

 

주어진 상태코드에 대한 사용자 정의 HTML 을 생성해서 /error 폴더에 추가만 하면 동작한다고 합니다.

 

예를 들어 404 에러를 HTML 파일에 연결하려면 아래와 같은 폴더구조가 됩니다.

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>
 

 

 

 

 

 

FreeMarker 템플릿을 사용해서 모든 오류를 연결하려면 아래와 같은 구조가 됩니다.

 

5xx 으로 시작되는 모든 에러를 하나의 HTML 파일에 맵핑한다는 것이죠.

src / 
 + - main / 
     + - java / 
     | + <소스 코드> 
     + - 리소스 / 
         + - 템플릿 / 
             + - 오류 / 
             | + - 5xx.ftl 
             + - <다른 템플릿>
 

 

 

 

Spring Boot 2.x 에는 static 폴더 아래에 error 페이지를 만들어서 진행하면 됩니다.

 

이게 끝입니다. 그냥 폴더만들고 에러코드와 맞는 이름으로 html파일만 생성하면 됩니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>404 Page Not Found</title>
</head>
<body>
    404 Error
</body>
</html>
 

 

 

 

 

 

 

임의의 url로 이동했을시에 정상적으로 아래와같은 페이지가 보여질 것입니다.

 

 

 

 

 

Spring Boot 에서 제공하는 Custom Error Page 처리에 알아보았습니다. 

 

ErrorController 를 구현하면 동작하지 않으니 주의 바랍니다.

 

728x90
반응형
728x90
반응형

 

Spring Boot Project 를 만들면 Maven 이나 Gradle이 기본적으로 설치되어있습니다.

 

다양한 library 을 쉽고 간편하게 가져와서 쓸수 있지만 외부와의 통신을 허용하지 않는 개발 환경이 있기도 합니다.

 

이럴때에는 외부 Jar 를 등록시켜야 하는데요.

 

Gradle 에서 외부 Jar를 등록해보도록 하겠습니다.

 

 

 

 

 

이전에 소개했던 Virtual Key를 기준으로 진행하겠습니다.

 

https://gigas-blog.tistory.com/101

 

[Java] Webponent Virtual Key 샘플 구현

이전에 소개했던 가성비 좋은 HTML 기반 보안키패드를 구현해보려고 합니다. https://gigas-blog.tistory.com/100 WebPonent Virtual Key - HTML 보안 키패드 웹과 하이브리드 앱에서 사용할 수 있는 보안키패드를..

gigas-blog.tistory.com

 

 

 

 

등록 방법은 아래와 같습니다.

 

1. gradle 프로젝트를 생성합니다.

 

2. 프로젝트 경로에 libs 라는 폴더를 생성합니다.

3. build.gradle 파일을 열어서 dependencies 부분에 함수를 호출합니다.

 

dependencies {

	// libs 폴더 특정 jar 파일 스캔
    implementation files('libs/eltz-keypad.jar')

    // libs 폴더 전체 jar 파일 스캔
    implementation fileTree(dir: 'libs', include: '*.jar')

...
}

 

4. gradle 을 동기화 합니다.

 

 

 

프로젝트 경로에 libs 파일을 생성하고 jar파일을 넣어 files() 라는 함수로 특정 jar만 스캔할 수 있습니다.

 

하지면 여러개의 jar 파일을 스캔한다면 매번 등록을 해줘야하는 번거로움이 있습니다.

 

그럴때에는 fileTree 함수를 사용해서 jar 파일을 전체 스캔하는 방법을 사용합니다.

 

 

 

 

어렵지 않은 작업이지만 정보가 없다면 어렵게 느껴질 수 있습니다.

 

더 많은 정보를 올릴수 있도록 노력하겠습니다.

 

728x90
반응형
728x90
반응형

 

Spring Boot 에서는 @ 로 시작하는 어노테이션을 많이 사용하게 됩니다.

 

자동으로 설정을 위해서인데 내부 소스에 축약되다보니 이해하는데 많은 불편함은 있습니다.

 

하지만 한두번 사용하다 보면 정말 편하구나라는게 더 크죠.

 

Spring Boot 는 XML의 설정보다 Java 소스 설정을 선호한다고합니다.

 

이번엔 @Cofiguration 을 알아보겠습니다.

 

 

 

 

 

 

Eclipse 프로그램을 사용하여 Spring Stater Project 를 생성했습니다.

 

config 라는 package를 생성해주고 안에 ExampleService.java 를 만들었습니다.

 

package com.gigas.config;

public class ExampleService {

}

 

 

 

다른 설정없으 src/test/java 안에있는 SpringBootTestApplicationTests.java 로 테스트 해보겠습니다.

 

 

 

@Autowired 를 통해 만들어둔 ExampleService를 의존주입 했습니다.

 

contextLoads() 안에서 assertNotNull() 함수의 인자로 exalpleService 변수를 넣고 값이 비었는지 테스트를 진행합니다.

package com.gigas;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.gigas.config.ExampleService;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootTestApplicationTests {

    @Autowired
    ExampleService exampleService;
    
    @Test
    public void contextLoads() {
        
        assertNotNull(exampleService);
    }

}
 

 

 

 

 

SpringBootTestApplicationTests.java 마우스 우클릭하여 JUnit Test 를 실행합니다.

 

 

 

 

테스트 결과는 당연히 Error 입니다.

 

 

 

오류 내용을 보면 유효한 Bean이 없다고 나옵니다.

 

즉 Bean이라고 설정한 파일이 아닌데 @AutoWired 어노테이션을 통해 의존주입을 하려고 해서입니다.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.gigas.SpringBootTestApplicationTests': Unsatisfied dependency expressed through field 'exampleService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.gigas.config.ExampleService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1411)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:391)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:119)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
    at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:44)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.gigas.config.ExampleService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1654)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1213)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593)
    ... 29 more
 
 

 

 

ExampleService 에 @Configuration 어노테이션을 추가합니다.

 

이 어노테이션으로 Bean을 자바에서 등록해줄수 있습니다.

package com.gigas.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class ExampleService {

}
 

 

 

 

다시 JUnit Test 를 진행합니다.

 

정상적으로 Bean이 등록되어있는 ExampleService 는 @AutoWired 를 통해 의존주입니 정상적으로 된것을 확인할 수 있습니다.

 

 

728x90
반응형

+ Recent posts