<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>meatlov.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Wed, 27 Oct 2021 04:46:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. meatlov.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jjun_meatlov" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[ResourceHandler 적용하기]]></title>
            <link>https://velog.io/@jjun_meatlov/ResourceHandler-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jjun_meatlov/ResourceHandler-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 27 Oct 2021 04:46:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/jjun_meatlov/post/a1a6fde7-361f-4d2c-a3ab-4f40a6d71452/99A16A435B6C4FF715.png" alt="">
이번 프로젝트에서 React와 Spring을 빌드하여 하나의 앱으로 배포했다</p>
<h3 id="whitelabel-error">WhiteLabel Error</h3>
<p>어떤 페이지로 접속을 했는데
그런데 React로 보여야할 페이지가 나오지 않고 Spring 서버로 Get요청이 들어갔다..
그래서 Whitelabel Error Page가 나오는데
<img src="https://images.velog.io/images/jjun_meatlov/post/9156eb38-20e1-44b6-89f3-c16c324c6914/image.png" alt="">
이때 생각난 것은 저번 프로젝트에서 정적 리소스 접근에 대한 설정을 했던 것이다</p>
<h3 id="해결-방법">해결 방법</h3>
<p>WebMvcConfigurer를 구현하는 Config 클래스를 생성하고
addResourceHandlers(registry: ResourceHandlerRegistry) 메서드를 override하여 그 안에 다음 코드를 작성한다
정적 리소스 위치는 각자의 환경에 맞게 설정한다</p>
<pre><code class="language-kotlin">registry.addResourceHandler(&quot;/**&quot;)
    .addResourceLocations(&quot;classpath:/dist/&quot;)
    .resourceChain(true)
    .addResolver(object : PathResourceResolver(){
        override fun getResource(resourcePath: String, location: Resource): Resource? {
                val requestResource = location.createRelative(resourcePath)
                    return if(requestResource.exists() &amp;&amp; requestResource.isReadable) requestResource
                    else ClassPathResource(&quot;/dist/index.html&quot;)
                }
        })</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jenkins로 배포 자동화하기]]></title>
            <link>https://velog.io/@jjun_meatlov/Jenkins%EB%A1%9C-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jjun_meatlov/Jenkins%EB%A1%9C-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 16 Aug 2021 03:25:54 GMT</pubDate>
            <description><![CDATA[<p>Jenkins와 Gitlab으로 배포 자동화를 해보자</p>
<p>서버는 AWS ec2를 사용하며, 앱은 Docker 컨테이너에서 동작한다</p>
<h2 id="1-jenkins-설정">1. Jenkins 설정</h2>
<ul>
<li>gradle, nodejs, github/gitlab 등 플러그인을 설치하고 설정을 시작한다<h3 id="jenkins-관리--시스템-설정">Jenkins 관리 &gt; 시스템 설정</h3>
</li>
<li>Jenkins가 실행중인 서버의 URL 작성
<img src="https://images.velog.io/images/jjun_meatlov/post/932b1d54-1915-4b43-ac99-a344419cfad8/1.png" alt=""></li>
<li>Gitlab / Github URL 작성
  Git 접근을 위해 AccessToken을 받아 Credential에 설정한다
  <img src="https://images.velog.io/images/jjun_meatlov/post/67dde680-b590-4308-b8b8-078343fce950/2.png" alt="">
  <img src="https://images.velog.io/images/jjun_meatlov/post/523e9789-d7ec-4cac-b1a3-677d17b3961c/image.png" alt=""></li>
<li>ssh로 서버에 접속하기 위한 설정
  Remote Directory 는 ssh 접속 시 들어가는 경로. 꼭 설정할 것
  Docker 명령어를 실행할 디렉토리
  <img src="https://images.velog.io/images/jjun_meatlov/post/5b6083b6-2202-421c-98ef-a805feb01e8e/ssh%EC%84%A4%EC%A0%95.png" alt=""></li>
<li>프로젝트 빌드 설정 (Gradle, Maven)
  사용할 gradle 버전 설정
<img src="https://images.velog.io/images/jjun_meatlov/post/1332db88-d86d-44c2-a28c-ab6bb50c48ce/image.png" alt=""></li>
<li>NodeJS 설정
<img src="https://images.velog.io/images/jjun_meatlov/post/0d985d15-0083-4f7f-b64e-7f7a977b7755/image.png" alt=""><h3 id="프로젝트-생성--설정">프로젝트 생성 &amp; 설정</h3>
</li>
<li>freestyle project 로 생성</li>
<li>소스코드 관리
  빌드할 repository의 url, 브랜치 설정
  인증을 위한 Credential(계정) 설정 (Username with password)
  <img src="https://images.velog.io/images/jjun_meatlov/post/55fb6f61-3df9-4793-8dae-b65692fdac8f/%EC%86%8C%EC%8A%A4%EC%BD%94%EB%93%9C%20%EA%B4%80%EB%A6%AC.png" alt=""></li>
<li>빌드 유발
  원하는 빌드 유발 설정을 한다
  GitLab webhook 설정에 아래  webhook URL을 사용하니 이 부분을 꼭 기억하자
  <img src="https://images.velog.io/images/jjun_meatlov/post/8667ea3b-58ae-456b-9dbd-6e876468e3f2/%EB%B9%8C%EB%93%9C%EC%9C%A0%EB%B0%9C.png" alt="">
  Webhook 설정을 위한 Secret token을 발급한다
<img src="https://images.velog.io/images/jjun_meatlov/post/d25c722f-3154-4222-b722-460866d72595/image.png" alt=""></li>
<li>빌드 환경 설정
<img src="https://images.velog.io/images/jjun_meatlov/post/b4fd0a39-0a94-4702-9781-4333669998f2/image.png" alt=""></li>
<li>빌드 시 필요한 명령어를 작성한다
  빌드에 필요하지만 버전 관리 되지 않는 파일은 Filezilla로 업로드하여 명령어를 통해 복사했다
<img src="https://images.velog.io/images/jjun_meatlov/post/44a8a0d7-fa3d-47c0-bfaa-3b8bccf1ba23/image.png" alt="">
<img src="https://images.velog.io/images/jjun_meatlov/post/e678d3c2-8a3d-4574-9a68-831630529a10/image.png" alt=""></li>
<li>빌드 후 조치
  ssh 연결을 위한 설정을 해주고 
<img src="https://images.velog.io/images/jjun_meatlov/post/837d66b5-ffe8-4356-86a7-aadd97236d55/image.png" alt="">
  docker 명령어를 실행할 위치로 jar 파일을 옮겨야 하니 그에 대한 설정을 한다
  Source files는 말그대로 옮길 파일을 특정한다
  Remove prefix는 Source files에 작성한 파일의 경로의 앞에서 지울 부분을 정한다
  Remote directory는 파일을 옮길 디렉토리다 (위에서 설정한 Root directory 다음부분을 쓴다)
  Exec command는 파일을 옮긴 후 실행할 명령어를 작성한다
<img src="https://images.velog.io/images/jjun_meatlov/post/bf37604c-7ea5-4f1e-b961-a0aadd582668/image.png" alt=""><h2 id="2-gitlab-설정">2. Gitlab 설정</h2>
</li>
<li>Webhook
  Jenkins 프로젝트 &gt; 구성 &gt; 빌드 유발 에서 발급한 토큰을 사용하고, 빌드 트리거를 설정한다
  <img src="https://images.velog.io/images/jjun_meatlov/post/f53527d0-fa8c-4659-92af-2b33ab067579/gitlab%EC%84%A4%EC%A0%95.png" alt="">
  <img src="https://images.velog.io/images/jjun_meatlov/post/0c8bdf05-6ae5-47c4-adfb-311ba56ae0bd/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[아이템 5] 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라]]></title>
            <link>https://velog.io/@jjun_meatlov/%EC%95%84%EC%9D%B4%ED%85%9C-5-%EC%9E%90%EC%9B%90%EC%9D%84-%EC%A7%81%EC%A0%91-%EB%AA%85%EC%8B%9C%ED%95%98%EC%A7%80-%EB%A7%90%EA%B3%A0-%EC%9D%98%EC%A1%B4-%EA%B0%9D%EC%B2%B4-%EC%A3%BC%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@jjun_meatlov/%EC%95%84%EC%9D%B4%ED%85%9C-5-%EC%9E%90%EC%9B%90%EC%9D%84-%EC%A7%81%EC%A0%91-%EB%AA%85%EC%8B%9C%ED%95%98%EC%A7%80-%EB%A7%90%EA%B3%A0-%EC%9D%98%EC%A1%B4-%EA%B0%9D%EC%B2%B4-%EC%A3%BC%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Thu, 29 Jul 2021 13:22:04 GMT</pubDate>
            <description><![CDATA[<h3 id="클래스-자원-의존">클래스, 자원 의존</h3>
<p>많은 클래스는 하나 이상의 자원에 의존한다
예) 사전에 의존하는 맞춤법 검사기 클래스</p>
<p>정적 유틸리티 클래스로 구현</p>
<blockquote>
<p>정적 유틸리티를 잘못 사용한 예
유연하지 않고 테스트하기 어렵다</p>
</blockquote>
<pre><code class="language-java">public class SpellChecker{
    private static final Lexicon dictionary = ...;

    private SpellChecker(){} // 인스턴스 생성 방지

    public static boolean isValid(String word){ ... }
    ...
}</code></pre>
<p>싱글턴도 가능</p>
<blockquote>
<p>싱글턴을 잘못 사용한 예
유연하지 않고 테스트하기 어렵다</p>
</blockquote>
<pre><code class="language-java">public class SpellChecker{
    private static final Lexicon dictionary = ...;

    private SpellChecker(...){}
    public static SpellChecker INSTANCE = new SpellChecker(...);

    public static boolean isValid(String word){ ... }
    ...
}</code></pre>
<p>둘 다 사전을 하나만 사용함 -&gt; 확장할 수 없다
자원에 따라 동작이 달라지는 클래스는 정적 유틸리티 클래스나 싱글턴이 적합하지 않다
사용자가 원하는 자원을 사용할 수 있게 해야하는데.. </p>
<p><strong>인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주면 된다!</strong></p>
<blockquote>
<p>의존 객체 주입 패턴</p>
</blockquote>
<pre><code class="language-java">public class SpellChecker{
    private static final Lexicon dictionary;

    public SpellChecker(Lexicon dictionary){
        this.dictionary = Objects.requireNonNull(dictionary);
    }

    public static boolean isValid(String word){ ... }
    ...
}</code></pre>
<p>자원이 몇 개든 의존 관계가 어떻든 상관없다
객체가 불변임을 보장하여 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있다
<strong>생성자, 정적 팩토리, 빌더</strong>에 똑같이 적용 가능</p>
<p>의존 객체 주입 프레임워크 Dagger, Guice, Spring</p>
<h3 id="정리">정리</h3>
<p>클래스가 내부적으로 하나 이상의 자원에 의존하고,
그 자원이 클래스 동작에 영향을 준다면 <strong>싱글턴</strong>과 <strong>정적 유틸리티 클래스</strong>는 사용하지 않는 것이 좋다.
자원들을 클래스가 직접 만들게 해서도 안된다.
대신, 필요한 자원 또는 자원을 만들어주는 팩토리를 <strong>생성자, 정적 팩토리, 빌더</strong>에 넘겨주자. 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 크게 개선해준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Data JPA 페이징 처리하기]]></title>
            <link>https://velog.io/@jjun_meatlov/Spring-Data-JPA-%ED%8E%98%EC%9D%B4%EC%A7%95-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jjun_meatlov/Spring-Data-JPA-%ED%8E%98%EC%9D%B4%EC%A7%95-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 25 Jul 2021 09:15:31 GMT</pubDate>
            <description><![CDATA[<p>Spring Data JPA를 사용하여 페이징 처리, 정렬을 해보자</p>
<h3 id="1-entity-생성">1. Entity 생성</h3>
<pre><code class="language-java">@Entity
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long no;
    private String userid;
    private String subject;
    private String content;
    ...
}</code></pre>
<h3 id="2-repository-생성">2. Repository 생성</h3>
<p>PagingAndSortingRepository를 상속한 BoardRepository를 만든다. PagingAndSortingRepository를 상속한 JPARepository를 상속해도 된다.</p>
<pre><code class="language-java">public interface BoardRepository extends PagingAndSortingRepository&lt;Board, Long&gt;{
    List&lt;Board&gt; findAllByUserId(String userId, Pageable pageable);
}</code></pre>
<p>PagingAndSortingRepository 인터페이스를 보자. findAll(Sort sort), findAll(Pageable pageable) 메서드가 있다.</p>
<pre><code class="language-java">@NoRepositoryBean
public interface PagingAndSortingRepository&lt;T, ID&gt; extends CrudRepository&lt;T, ID&gt; {
    /**
     * Returns all entities sorted by the given options.
     *
     * @param sort
     * @return all entities sorted by the given options
     */
    Iterable&lt;T&gt; findAll(Sort sort);

    /**
     * Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
     *
     * @param pageable
     * @return a page of entities
     */
    Page&lt;T&gt; findAll(Pageable pageable);
}</code></pre>
<h3 id="3-페이징-처리">3. 페이징 처리</h3>
<p>이제 데이터를 조회해보자
PageRequest 객체를 생성하고 findAll 메서드에 파라미터로 전달한다.
페이지 번호는 0부터 시작하므로 pno에서 1을 뺀 값을 전달한다.</p>
<pre><code class="language-java">// 10개씩 나누어 페이징하고, 페이지 번호가 pno-1인 데이터들을 가져온다
Pageable pageWithTenElements = PageRequest.of(pno - 1, 10, Direction.DESC, &quot;no&quot;);
noticeRepo.findAll(pageable);</code></pre>
<h3 id="4-페이징-및-정렬">4. 페이징 및 정렬</h3>
<p>정렬된 결과를 얻으려면 메서드에 Sort 객체를 전달한다.</p>
<pre><code class="language-java">Page&lt;Board&gt; boardsSortedByUserId = boardRepository.findAll(Sort.by(&quot;userId&quot;));</code></pre>
<p>페이징과 정렬 둘 다 하고싶으면 아래 코드처럼 PageRequest 객체를 생성하여 사용한다.</p>
<pre><code class="language-java">Pageable sortedByUserId = 
  PageRequest.of(0, 3, Sort.by(&quot;userId&quot;));

Pageable sortedByUserIdDesc = 
  PageRequest.of(0, 3, Sort.by(&quot;userId&quot;).descending());

Pageable sortedByUserIdDescNoAsc = 
  PageRequest.of(0, 5, Sort.by(&quot;userId&quot;).descending().and(Sort.by(&quot;no&quot;)));</code></pre>
<blockquote>
<p>참고한 Baeldung 문서
<a href="https://www.baeldung.com/spring-data-jpa-pagination-sorting">https://www.baeldung.com/spring-data-jpa-pagination-sorting</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[아이템 6] 불필요한 객체 생성을 피하라]]></title>
            <link>https://velog.io/@jjun_meatlov/%EC%95%84%EC%9D%B4%ED%85%9C-6-%EB%B6%88%ED%95%84%EC%9A%94%ED%95%9C-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EC%9D%84-%ED%94%BC%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@jjun_meatlov/%EC%95%84%EC%9D%B4%ED%85%9C-6-%EB%B6%88%ED%95%84%EC%9A%94%ED%95%9C-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EC%9D%84-%ED%94%BC%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sat, 24 Jul 2021 12:12:50 GMT</pubDate>
            <description><![CDATA[<p>같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 것이 좋을 때가 많다. 재사용은 빠르고 세련되다. 특히 불변 객체는 언제든 재사용할 수 있다.</p>
<p>생성자 대신 정적 팩터리 메서드를 제공하는 불변 클래스에서는 정적팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다.</p>
<pre><code class="language-java">new Boolean(&quot;true&quot;);
// 위(생성자) 대신 아래 코드를 사용한다
Boolean.valueOf(&quot;true&quot;);</code></pre>
<h3 id="생성-비용이-비싼-객체가-있으면-재사용하자">생성 비용이 비싼 객체가 있으면 재사용하자</h3>
<p>다음과 같은 예시가 있다.</p>
<pre><code class="language-java">static boolean isRomanNumeral(String s){
    return s.matches(&quot;^(?=.)M*(C[MD]|D?C{0,3}&quot;
            + &quot;((X[CL]|L?X{0,3})(I[XV]|v?I{0,3})$&quot;;
}</code></pre>
<p>String.matches()의 Pattern 인스턴스는 한 번 쓰고 버려지는데, Pattern은 입력받은 정규표현식에 해당하는 유한 상태 머신을 만들기 때문에 인스턴스 생성 비용이 높고, 그래서 그 객체를 재사용해서 성능을 개선할 수 있다고 한다.</p>
<pre><code class="language-java">// 성능 개선
public class RomanNumerals{
    private static final Pattern ROMAN = Pattern.compile(
            &quot;^(?=.)M*(C[MD]|D?C{0,3}&quot;
            + &quot;((X[CL]|L?X{0,3})(I[XV]|v?I{0,3})$&quot;);
    static boolean isRomanNumeral(String s){
        return ROMAN.matchers(s).matches();
    }
}</code></pre>
<h3 id="오토박싱으로-인해-불필요한-객체가-생성되지-않도록-하자">오토박싱으로 인해 불필요한 객체가 생성되지 않도록 하자</h3>
<pre><code class="language-java">Long sum = 0L;
for(long i = 0; i &lt;= Integer.MAX_VALUE; i++){
    sum += i;
}</code></pre>
<p>Long 타입의 sum 변수에 i를 더할 때마다 오토박싱으로 인해 불필요한 Long 인스턴스가 생성된다. 그래서 제대로 짠 코드보다 훨씬 느리다.
sum 변수를 long 타입으로 바꾸자. 저자는 Long을 사용할 때보다 실행 시간이 10배 빨라졌다고 한다.</p>
<h3 id="불필요한-객체-생성을-피한-예">불필요한 객체 생성을 피한 예</h3>
<p>Map인터페이스의 keySet 메서드가 반환하는 Set객체를 보면 알 수 있다.
이 코드를 실행하면 keySet2에서 1이 사라진 것을 알 수 있다.</p>
<pre><code class="language-java">Map&lt;Integer, String&gt; map = new HashMap&lt;&gt;();
map.put(1,&quot;one&quot;);
map.put(2,&quot;two&quot;);
map.put(3,&quot;three&quot;);
Set&lt;Integer&gt; keySet1 = map.keySet();
Set&lt;Integer&gt; keySet2 = map.keySet();
keySet1.remove(1);
for (int i : keySet2){
    System.out.println(i);
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[생성자 주입 사용하기]]></title>
            <link>https://velog.io/@jjun_meatlov/%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jjun_meatlov/%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 23 Jul 2021 13:32:45 GMT</pubDate>
            <description><![CDATA[<p>나는 스프링에서 필드 의존성 주입을 하라고 배웠다
<img src="https://images.velog.io/images/jjun_meatlov/post/0149e154-d385-4e76-a1e6-8a17e6ca1c73/image.png" alt="i">
그런데 인텔리제이가 Field injection을 하지말라고 경고한다.
그래서 그에 대한 내용을 찾아봤다.</p>
<h3 id="스프링에서-권장하는-일반적인-di-가이드라인은-다음과-같다">스프링에서 권장하는 일반적인 DI 가이드라인은 다음과 같다.</h3>
<ul>
<li>필수 의존성이나 불변성이 목적이면 생성자 주입을 사용한다.</li>
<li>선택적 또는 변경 가능한 의존성에는 setter 주입을 사용한다.</li>
<li>웬만하면 필드 주입을 피하라.</li>
</ul>
<h3 id="field-injection의-단점">Field Injection의 단점</h3>
<ul>
<li>생성자 주입처럼 변경할 수 없는 객체를 만들 수 없다.</li>
<li>클래스는 DI 컨테이너와 긴밀하게 연결되어 있으며 외부에서 사용할 수 없다.</li>
<li>리플렉션 없이는 클래스를 인스턴스화할 수 없다. 인스턴스화하려면 DI 컨테이너가 필요하므로 테스트를 통합 테스트와 유사하게 만든다.</li>
<li>실제 의존성은 외부에서 숨겨져 있으며 인터페이스(생성자 또는 메서드)에 반영되지 않는다.</li>
</ul>
<h3 id="결론">결론</h3>
<p>필요에 따라 생성자 주입을 사용하거나 생성자와 setter 주입을 혼합하여 사용해야 한다. 필드 주입에는 많은 단점이 있으므로 피해야 한다. 유일한 장점은 쓰기가 더 편리하다는 것이다.</p>
<p><a href="https://stackoverflow.com/questions/39890849/what-exactly-is-field-injection-and-how-to-avoid-it">참고</a></p>
]]></description>
        </item>
    </channel>
</rss>