<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>binary_j.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 13 Nov 2023 22:50:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>binary_j.log</title>
            <url>https://images.velog.io/images/binary_j/profile/62e020ee-558e-407e-92c7-fffd14b2b7b8/Screenshot_20210922-144235_Gallery.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. binary_j.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/binary_j" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[챕터 9: 스프링 MVC 시작하기]]></title>
            <link>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-9-%EC%8A%A4%ED%94%84%EB%A7%81-MVC-%EC%8B%9C%EC%9E%91%ED%95%98</link>
            <guid>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-9-%EC%8A%A4%ED%94%84%EB%A7%81-MVC-%EC%8B%9C%EC%9E%91%ED%95%98</guid>
            <pubDate>Mon, 13 Nov 2023 22:50:29 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-생성">프로젝트 생성</h2>
<hr>
<p>앞과 동일하게 프로젝트를 생성한다.
웹을 위한 디렉토리를 추가해야 한다.</p>
<ul>
<li>src/main/webapp</li>
<li>src/main/webapp/WEB-INF</li>
<li>src/main/webapp/WEB-INF/view</li>
</ul>
<p>webapp은 HTML, CSS, JS, JSP 등 웹 어플리케이션을 구현하는데 필요한 코드가 위치하며 WEB-INF에는 web.xml 파일이 위치한다.</p>
<pre><code>&lt;packaging&gt;war&lt;/packaging&gt;

    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
            &lt;artifactId&gt;javax.servlet-api&lt;/artifactId&gt;
            &lt;version&gt;3.1.0&lt;/version&gt;
            &lt;scope&gt;provided&lt;/scope&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;javax.servlet.jsp&lt;/groupId&gt;
            &lt;artifactId&gt;javax.servlet.jsp-api&lt;/artifactId&gt;
            &lt;version&gt;2.3.2-b02&lt;/version&gt;
            &lt;scope&gt;provided&lt;/scope&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
            &lt;artifactId&gt;jstl&lt;/artifactId&gt;
            &lt;version&gt;1.2&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework&lt;/groupId&gt;
            &lt;artifactId&gt;spring-webmvc&lt;/artifactId&gt;
            &lt;version&gt;5.1.6.RELEASE&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;</code></pre><p>패키징 war, 웹 어플리케이션 개발에 필요한 spring-webmvc 모듈과 서블릿을 추가한다.</p>
<h2 id="이클립스-톰캣-설정">이클립스 톰캣 설정</h2>
<hr>
<p>톰캣 8.5 버전을 다운로드 한 후 이클립스 서버 설정에 톰캣을 추가한다.</p>
<h2 id="스프링-mvc를-위한-설정">스프링 MVC를 위한 설정</h2>
<hr>
<h3 id="스프링-mvc-설정">스프링 MVC 설정</h3>
<pre><code>package config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp(&quot;/WEB-INF/view/&quot;, &quot;.jsp&quot;);
    }

}</code></pre><p>@EnableWenMvc 애노테이션을 사용하면 스프링 MVC를 사용하는데 필요한 기본적인 구성을 알아서 설정해준다.
WebMvcConfigurer을 사용하여 개별 설정을 조정할 수도 있다.</p>
<h3 id="dispatcherservlet-설정">DispatcherServlet 설정</h3>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;

&lt;web-app xmlns=&quot;http://xmlns.jcp.org/xml/ns/javaee&quot; 
    xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
    xsi:schemaLocation=&quot;http://xmlns.jcp.org/xml/ns/javaee 
             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd&quot;
    version=&quot;3.1&quot;&gt;

    &lt;servlet&gt;
        &lt;servlet-name&gt;dispatcher&lt;/servlet-name&gt;
        &lt;servlet-class&gt;
            org.springframework.web.servlet.DispatcherServlet
        &lt;/servlet-class&gt;
        &lt;init-param&gt;
            &lt;param-name&gt;contextClass&lt;/param-name&gt;
            &lt;param-value&gt;
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            &lt;/param-value&gt;
        &lt;/init-param&gt;
        &lt;init-param&gt;
            &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
            &lt;param-value&gt;
                config.MvcConfig
                config.ControllerConfig
            &lt;/param-value&gt;
        &lt;/init-param&gt;
        &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
    &lt;/servlet&gt;

    &lt;servlet-mapping&gt;
        &lt;servlet-name&gt;dispatcher&lt;/servlet-name&gt;
        &lt;url-pattern&gt;/&lt;/url-pattern&gt;
    &lt;/servlet-mapping&gt;

    &lt;filter&gt;
        &lt;filter-name&gt;encodingFilter&lt;/filter-name&gt;
        &lt;filter-class&gt;
            org.springframework.web.filter.CharacterEncodingFilter
        &lt;/filter-class&gt;
        &lt;init-param&gt;
            &lt;param-name&gt;encoding&lt;/param-name&gt;
            &lt;param-value&gt;UTF-8&lt;/param-value&gt;
        &lt;/init-param&gt;
    &lt;/filter&gt;
    &lt;filter-mapping&gt;
        &lt;filter-name&gt;encodingFilter&lt;/filter-name&gt;
        &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
    &lt;/filter-mapping&gt;

&lt;/web-app&gt;</code></pre><p>DispatcherServlet은 초기화 과정에서 contextCOnfiguration 초기화 파라미터에 지정한 파일을 사용하여 스프링 컨테이너를 초기화한다.</p>
<h2 id="코드-구현">코드 구현</h2>
<hr>
<pre><code>package chap09;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class HelloController {

    @GetMapping(&quot;/hello&quot;)
    public String hello(Model model,
            @RequestParam(value = &quot;name&quot;, required = false) String name) {
        model.addAttribute(&quot;greeting&quot;, &quot;안녕하세요, &quot; + name);
        return &quot;hello&quot;;
    }
}</code></pre><p>@Controller 애노테이션: 스프링 MVC에서 컨트롤러로 사용
@GetMapping: 요청 경로 지정
@RequestParam 애노테이션: HTTP 요청 파라미터의 값을 메서드의 파라미터로 전달</p>
<ul>
<li>컨트롤러: 웹 요청을 처리하고 결과를 뷰에 전달하는 빈 객체</li>
<li>@Mapping: 서블릿 컨텍스트 경로(웹 어플리케이션 경로) 기준으로 URL 처리</li>
</ul>
<h3 id="jsp-구현">JSP 구현</h3>
<pre><code>&lt;%@ page contentType=&quot;text/html; charset=utf-8&quot; %&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Hello&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    인사말: ${greeting}
  &lt;/body&gt;
&lt;/html&gt;</code></pre><p>src/main/java/webapp/WEB-INF/view 폴더에 jsp 파일 생성.
설정 파일의 registry.jsp()는 JSP를 뷰 구현으로 사용할 수 있도록 해주는 설정</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[챕터 8: DB 연동]]></title>
            <link>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-8-DB-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-8-DB-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Mon, 13 Nov 2023 21:53:09 GMT</pubDate>
            <description><![CDATA[<p>스프링은 JDBC에서 반복되는 코드들을 JdbcTemplate 클래스로 제공해준다. 이 클래스를 사용하면 중복되는 코드를 효과적으로 줄일 수 있다.</p>
<p>또한 스프링은 트랜잭션 관리 기능을 애노테이션으로 제공한다.(@Transactional) 커밋과 롤백은 스프링이 알아서 처리해준다.</p>
<p>DB 설치 후 테스트용 기본 테이블을 생성하는 과정은 넘어가도록 하겠다.</p>
<h2 id="참고-pomxml">참고) pom.xml</h2>
<hr>
<p>책이 조금 오래전에 나왔기 때문에 최신 버전과 호환되지 않을 수 있다.
난 의존성을 내 MySQL 버전에 맞게 다음과 같이 설정하였다.</p>
<pre><code>&lt;dependency&gt;
            &lt;groupId&gt;org.springframework&lt;/groupId&gt;
            &lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt;
            &lt;version&gt;5.1.6.RELEASE&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.tomcat&lt;/groupId&gt;
            &lt;artifactId&gt;tomcat-jdbc&lt;/artifactId&gt;
            &lt;version&gt;8.5.65&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.mysql&lt;/groupId&gt;
            &lt;artifactId&gt;mysql-connector-j&lt;/artifactId&gt;
            &lt;version&gt;8.0.33&lt;/version&gt;
        &lt;/dependency&gt;</code></pre><h2 id="datasource-설정">DataSource 설정</h2>
<hr>
<p>스프링이 제공하는 DB 연동 기능은 DataSource를 사용하여 DB Connection을 구한다.</p>
<p>DB 연동에 사용할 DataSource를 스프링 빈으로 등록하고 DB 연동 기능을 구현한 빈 객체는 DataSource를 주입받아 사용한다.</p>
<pre><code>package config;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppCtx {

    @Bean(destroyMethod = &quot;close&quot;)
    public DataSource dataSource() {
        DataSource ds = new DataSource();
        ds.setDriverClassName(&quot;com.mysql.jdbc.Driver&quot;);
        ds.setUrl(&quot;jdbc:mysql://localhost/spring5fs?characterEncoding=utf8&quot;);
        ds.setUsername(&quot;spring5&quot;);
        ds.setPassword(&quot;spring5&quot;);
        ds.setInitialSize(2);
        ds.setMaxActive(10);
        return ds;
    }

}</code></pre><p>DataSource 객체를 생성하고 MySQL DB 관련 설정을 하는 코드이다.</p>
<pre><code>package dbquery;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.tomcat.jdbc.pool.DataSource;

public class DbQuery {

    private DataSource dataSource;

    public DbQuery(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public int count() {
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            try (Statement stmt = conn.createStatement();
                    ResultSet rs = stmt.executeQuery(&quot;select count(*) from MEMBER&quot;)) {
                rs.next();
                return rs.getInt(1);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            if(conn != null)
                try {
                    conn.close();
                } catch(SQLException e) {
                }
        }
    }

}</code></pre><p>커넥션을 구하고 종료하는 코드이다.</p>
<p>커넥션을 구할 때는 커넥션 풀에서 커넥션을 가져와서 사용한다. 사용이 완료된 후에는 커넥션을 다시 반환한다. 반환된 커넥션은 유휴 상태로 대기하게 된다.</p>
<ul>
<li>maxActive: 활성 상태가 가능한 최대 커넥션 수</li>
<li>maxWait: 이미 커넥션이 최대로 활성화 되어있을 때 새 커넥션을 요청하면 다른 커넥션이 반환될 때까지 대기하는 최대 대기 시간(반환 X면 익셉션 발생)</li>
</ul>
<p>커넥션 풀을 사용하면 미리 커넥션을 생성해놓고 꺼내와서 쓸 수 있기 때문에 성능적으로 더 유리하다.</p>
<p>일정 시간 이상 쿼리가 실행되지 않으면 커넥션을 끊어버리는 DBMS도 있기 때문에 때때로 지속적으로 커넥션을 테스트 해야되는 경우도 존재한다. 이런 경우 다음과 같은 속성을 사용하면 된다.</p>
<ul>
<li>minEvictableTimeMillis: 최소 유휴 시간</li>
<li>timeBetweenEvictionRunsMillis: 검사 주기</li>
<li>testWhileIdle: 유휴 커넥션 검사</li>
</ul>
<h2 id="jdbc-template-생성하기">JDBC Template 생성하기</h2>
<hr>
<p>스프링을 사용하면 JDBC Template을 사용하여 편리하게 쿼리를 실행할 수 있다.</p>
<h3 id="jdbc-template-생성">JDBC Template 생성</h3>
<pre><code>private JdbcTemplate jdbcTemplate;

    public MemberDao(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }</code></pre><p>JDBCTemplate 객체 생성.</p>
<p>DataSource를 주입받아 JDBCTemplate 객체를 생성하도록 했다.</p>
<h3 id="jdbc-template을-이용한-조회-쿼리-실행">JDBC Template을 이용한 조회 쿼리 실행</h3>
<p>JDBCTemplate의 query() 메서드는 SELECT 쿼리를 실행한 후 실행 결과를 RowMapper 인터페이스로 자바 객체로 변환해준다.</p>
<pre><code>public Member selectByEmail(String email) {
        List&lt;Member&gt; results = jdbcTemplate.query(
                &quot;select * from MEMBER where EMAIL = ?&quot;,
                new RowMapper&lt;Member&gt;() {
                    @Override
                    public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
                        Member member = new Member(
                                rs.getString(&quot;EMAIL&quot;),
                                rs.getString(&quot;PASSWORD&quot;),
                                rs.getString(&quot;NAME&quot;),
                                rs.getTimestamp(&quot;REGDATE&quot;).toLocalDateTime());
                        member.setId(rs.getLong(&quot;ID&quot;));
                        return member;
                    }
                }, email);

        return results.isEmpty() ? null : results.get(0);
    }</code></pre><p>임의 클래스로 이메일을 조회하는 코드이다.</p>
<p>결과가 한 행밖에 없을 경우에는 List보다는 Integer 같은 타입으로 결과를 받는 것이 간편할 것이다. 이럴 때는 queryForObject()를 사용하면 된다.</p>
<pre><code>public int count() {
        Integer count = jdbcTemplate.queryForObject(
                &quot;select count(*) from MEMBER&quot;, Integer.class);
        return count;
    }</code></pre><p>queryForObject()는 실행 결과가 0개 거나 두 개 이상이면 익셉션을 발생기키기 때문에 반드시 실행 결과가 한 행이어야 한다.</p>
<h3 id="jdbc-template을-이용한-변경-쿼리-실행">JDBC Template을 이용한 변경 쿼리 실행</h3>
<p>INSERT, UPDATE, DELETE 쿼리는 모두 update()를 사용한다.
update()의 리턴값은 변경된 행의 개수이다.</p>
<pre><code>public void update(Member member) {
        jdbcTemplate.update(
                &quot;update MEMBER set NAME = ?, PASSWORD = ? where EMAIL = ?&quot;,
                member.getName(), member.getPassword(), member.getEmail());
    }</code></pre><h3 id="preparedstatementcreator를-이용한-쿼리-실행">PreparedStatementCreator를 이용한 쿼리 실행</h3>
<p>PreparedStatement의 set 메서드를 사용해서 직접 인덱스 파라미터의 값을 설정해야 하는 경우에는 PreparedStatementCreator를 인자로 받는 메서드를 이용해서 직접 PreparedStatement를 생성하고 설정해야 한다.</p>
<h3 id="insert-쿼리-실행-시-keyholder를-이용해서-자동-생성-키값-구하기">INSERT 쿼리 실행 시 KeyHolder를 이용해서 자동 생성 키값 구하기</h3>
<p>MySQL의 AUTO_INCREMENT 컬럼은 행이 추가되면 자동으로 값이 할당되며 주요키 컬럼에 사용된다.</p>
<p>쿼리 실행 후에 해당 키값을 알고 싶다면 JdbcTemplate의 KeyHolder를 사용할 수 있다.</p>
<pre><code>public void insert(Member member) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection con)
                    throws SQLException {
                // 파라미터로 전달받은 Connection을 이용해서 PreparedStatement 생성
                PreparedStatement pstmt = con.prepareStatement(
                        &quot;insert into MEMBER (EMAIL, PASSWORD, NAME, REGDATE) &quot; +
                        &quot;values (?, ?, ?, ?)&quot;,
                        new String[] { &quot;ID&quot; });
                // 인덱스 파라미터 값 설정
                pstmt.setString(1, member.getEmail());
                pstmt.setString(2, member.getPassword());
                pstmt.setString(3, member.getName());
                pstmt.setTimestamp(4,
                        Timestamp.valueOf(member.getRegisterDateTime()));
                // 생성한 PreparedStatement 객체 리턴
                return pstmt;
            }
        }, keyHolder);
        Number keyValue = keyHolder.getKey();
        member.setId(keyValue.longValue());
    }</code></pre><p>update() 메서드는 PreparedStatement를 실행한 후 자동 생성된 키 값을 KeyHolder에 보관한다. 이를 getKey() 메서드를 이용하여 구할 수 있다.</p>
<h2 id="스프링의-익셉션-변환-처리">스프링의 익셉션 변환 처리</h2>
<hr>
<p>스프링은 호환성을 위해 SQLException을 그대로 전파하지 않고 변환 처리해서 전달한다. 이렇게 하면 연동 기술에 상관없이 동일하게 익셉션을 처리할 수 있기 때문에 개발할 때 편리하다는 장점이 있다.</p>
<h2 id="트랜잭션-처리">트랜잭션 처리</h2>
<hr>
<p>여러 작업을 하나의 작업으로 묶어서 처리해야 하는 경우 트랜잭션을 사용할 수 있다.</p>
<h3 id="transactional을-이용한-트랜잭션-처리">@Transactional을 이용한 트랜잭션 처리</h3>
<p>트랜잭션 범위 내에서 실행하고 싶은 메서드에 @Transactional 애노테이션만 붙이면 간단하게 트랜잭션 처리를 할 수 있다.</p>
<p>@Transactional 애노테이션을 사용하려면 스프링 설정에 다음 두 가지를 추가해야 한다.</p>
<ul>
<li>플랫폼 트랜잭션 매니저(PlatformTransactionManager) 빈 설정: 
스프링이 제공하는 트랜잭션 매니저 인터페이스, 구현기술에 상관 없이 동일한 방식으로 트랜잭션 처리 가능</li>
<li>@Transactional 애노테이션 활성화 설정</li>
</ul>
<pre><code>@Configuration
@EnableTransactionManagement</code></pre><pre><code>@Bean
    public PlatformTransactionManager transactionManager() {
        DataSourceTransactionManager tm = new DataSourceTransactionManager();
        tm.setDataSource(dataSource());
        return tm;
    }</code></pre><p>제대로 실행되는지 로그를 확인하기 위해 Logback 기능을 사용해 보도록 하겠다.</p>
<pre><code>&lt;dependency&gt;
            &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
            &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
            &lt;version&gt;2.0.9&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
            &lt;artifactId&gt;logback-classic&lt;/artifactId&gt;
            &lt;version&gt;1.4.11&lt;/version&gt;
        &lt;/dependency&gt;</code></pre><p>pom.xml을 변경한 후 resources에 logback.xml을 설정해주면 로그 기능을 사용할 수 있다.</p>
<h3 id="트랜잭션과-프록시">트랜잭션과 프록시</h3>
<p>트랜잭션도 공통 기능 중 하나이기 때문에 내부적으로 AOP를 사용한다.
@Transactional 애노테이션이 적용되어 있는 경우 스프링은 트랜잭션 기능을 적용한 프록시 객체를 생성한다. 이 프록시 객체를 @Transactional이 붙은 메서드를 호출하면 PlatformTransactionManager를 사용해서 트랜잭션을 시작하고 성공적으로 실행된 경우 트랜잭션을 커밋한다.</p>
<h3 id="transactional-적용-메서드의-롤백-처리">@Transactional 적용 메서드의 롤백 처리</h3>
<p>별다른 설정이 존재하지 않는 경우 RuntimeException이 발생했을 때 트랜잭션을 롤백한다. SQLException은 RuntimeException을 상속하고 있지 않으므로 이 경우에는 트랜잭션을 롤백하지 않는다. SQLException이 발생했을 때에도 트랜잭션을 롤백하고 싶다면 @Transactional의 rollbackFor 설정을 사용해야 한다.</p>
<pre><code>Transactional(rollbackFor = SQLException.class)</code></pre><p>이와 반대의 기능을 제공하는 noRollbackFor 속성도 존재한다.
여러 익셉션 타입을 지정하고 싶다면 배열 형태로 지정하면 된다.</p>
<h3 id="transactional의-주요-속성">@Transactional의 주요 속성</h3>
<ul>
<li>value(String): 트랜잭션을 관리할 때 사용할 PlatformTransactionManager의 빈의 이름 지정. default &quot;&quot;</li>
<li>propagation(Propagation): 트랜잭션 전파 타입 지정. default Propagation.REQUIRED</li>
<li>isolation(Isolation): 트랜잭션 격리 레벨 지정. default Propagation.DEFAULT</li>
<li>timeout(int): 트랜잭션 제한 시간 지정. default -1</li>
</ul>
<h3 id="enabletransactionmanagement-애노테이션의-주요-속성">@EnableTransactionManagement 애노테이션의 주요 속성</h3>
<ul>
<li>proxyTargetClass: 클래스를 이용해서 프록시를 생성할지 여부 지정. default는 false로 인터페이슬르 이용해서 프록시를 생성한다.</li>
<li>order: AOP 적용 순서 지정.</li>
</ul>
<h3 id="트랜잭션-전파">트랜잭션 전파</h3>
<p>JDBCTemplate은 진행 중인 트랜잭션이 존재하면 해당 트랜잭션 범위 내에서 쿼리를 실행한다.
만약 새로운 트랜잭션이 시작되게 하고싶다면 propagation 속성값이 REQUIRED_NEW가 되게 하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[챕터 7: AOP 프로그래밍]]></title>
            <link>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-7-AOP-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-7-AOP-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Mon, 13 Nov 2023 10:03:42 GMT</pubDate>
            <description><![CDATA[<h2 id="프록시와-aop">프록시와 AOP</h2>
<hr>
<h3 id="프록시란">프록시란?</h3>
<p>핵심 기능의 실행은 다른 객체에 위임하고 부가적인 기능을 제공하는 객체</p>
<p>책에 나온 예제대로 팩토리얼의 실행 시간을 측정하는 객체: 프록시
실제로 팩토리얼 연산을 수행하는 객체: 대상 객체
라고 표현한다.</p>
<p>프록시는 핵심 기능을 구현하지 않는다. 대신 여러 객체에 공통으로 적용할 수 있는 기능을 구현한다. 이렇게 공통 기능과 핵심 기능을 나누어 구현하는 것이 AOP의 핵심이다.</p>
<h3 id="aop">AOP</h3>
<p>AOP(Aspect Oriented Programming)는 여러 객체에 공통으로 적용할 수 있는 기능을 분리하여 재사용성을 높여주는 프로그래밍 기법이다.
핵심 기능의 코드를 수정하지 않으면서 공통 기능을 추가할 수 있도록 해준다.</p>
<p>AOP에는 다음 세 가지 방법이 있다.</p>
<ul>
<li>컴파일 시점에 코드에 공통 기능을 삽입하는 방법</li>
<li>클래스 로딩 시점에 바이트 코드에 공통 기능을 삽입하는 방법</li>
<li>런타임에 프록시 객체를 생성해서 공통 기능을 삽입하는 방법</li>
</ul>
<p>첫번째와 두번째 방법은 스프링 AOP에서는 지원하지 않으며 AspectJ와 같은 AOP 전용 도구를 사용해야 한다.</p>
<p>스프링이 제공하는 AOP 방식은 세 번째 방식이다. 스프링 AOP는 프록시 객체를 자동으로 만들어주기 때문에 공통 기능을 구현한 클래스만 알맞게 구현하면 된다.</p>
<p>AOP의 용어</p>
<ul>
<li>Advice: 언제 공통 관심 기능을 핵심 로직에 적용할 지 정의</li>
<li>Jointpoint: Advice를 적용 가능한 지점</li>
<li>Pointcut: Jointpoint의 부분 집합으로서 실제 Advice가 적용되는 Jointpoint</li>
<li>Weaving: Advice를 핵심 로직 코드에 적용하는 것</li>
<li>Aspect: 여러 객체에 공통으로 적용되는 기능</li>
</ul>
<p>Advice의 종류</p>
<ul>
<li>Before Advice: 대상 객체의 메서드 호출 이전</li>
<li>After Running Advice: 대상 객체의 메서드가 익셉션 없이 실행된 이후</li>
<li>After Throwing Advice: 대상 객체의 메서드를 실행 중 익셉션이 발생한 경우</li>
<li>After Advice: 익셉션 발생 여부에 상관없이 대상 객체의 메서드 실행 후</li>
<li>Around Advice: 대상 객체의 메서드 실행 전, 후 또는 익셉션 발생 시점</li>
</ul>
<p>Around Advice가 가장 널리 사용된다.</p>
<h2 id="스프링-aop-구현">스프링 AOP 구현</h2>
<hr>
<ol>
<li>Aspect로 사용할 클래스에 @Aspect 애노테이션 붙임</li>
<li>@Pointcut 애노테이션으로 공통 기능을 적용할 Pointcut 정의</li>
<li>공통 기능을 구현한 메서드에 @Around 애노테이션 적용</li>
</ol>
<pre><code>package aspect;

import java.util.Arrays;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class ExeTimeAspect {

    @Pointcut(&quot;execution(public * chap07..*(..))&quot;)
    private void publicTarget() {
    }

    @Around(&quot;publicTarget()&quot;)
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.nanoTime();
        try {
            Object result = joinPoint.proceed();
            return result;
        } finally {
            long finish = System.nanoTime();
            Signature sig = joinPoint.getSignature();
            System.out.printf(&quot;%S.%s(%s) 실행 시간 : %d ns\n&quot;,
                    joinPoint.getTarget().getClass().getSimpleName(),
                    sig.getName(), Arrays.toString(joinPoint.getArgs()),
                    (finish - start));
        }
    }

}</code></pre><p>** 시그니처란? 자바에서 메서드 이름과 파라미터를 합쳐서 부르는 말</p>
<p>Aspect 애노테이션이 붙은 클래스를 공통 기능으로 적용하려면 스프링 설정 클래스에 @EnableAspectJAutoProxy 애노테이션을 추가해야 한다.</p>
<h2 id="프록시-생성-방식">프록시 생성 방식</h2>
<hr>
<p>스프링은 AOP를 위한 프록시 객체를 생성할 때 실제 생성할 빈 객체가 인터페이스를 상속하면 인터페이스를 이용해서 프록시를 생성한다.</p>
<p>빈 객체가 인터페이스를 상속할 때 인터페이스가 아닌 클래스를 이용해서 프록시를 생성하고 싶다면 다음과 같은 코드를 추가해야 한다.</p>
<pre><code>@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)</code></pre><p>이렇게 proxyTargetClass를 true로 설정하면 자바 클래스를 상속받아 프록시를 생성한다.</p>
<h3 id="execution-명시자-표현식">execution 명시자 표현식</h3>
<p>위에서 Aspect를 적용할 위치를 지정할 때 사용한 Pointcut 설정을 보면 execution 명시자를 사용하였다.</p>
<p>이 명시자는 Advice를 적용할 메서드를 지정한다. 기본적인 형식은 다음과 같다.</p>
<blockquote>
<p>execution(수식어패턴?리턴타입패턴 클래스이름패턴?메서드이름패턴(파라미터패턴))</p>
</blockquote>
<p>*은 모든 값, ..는 0개 이상을 뜻한다.</p>
<h3 id="advice-적용-순서">Advice 적용 순서</h3>
<p>한 Pointcut에 여러 Advice를 적용할 수도 있다.</p>
<pre><code>package aspect;

import java.util.HashMap;
import java.util.Map;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class CacheAspect {

    private Map&lt;Long, Object&gt; cache = new HashMap&lt;&gt;();

    @Pointcut(&quot;execution(public * chap07..*(long)&quot;)
    public void cahceTarget() {
    }

    @Around(&quot;cacheTarget()&quot;)
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        Long num = (Long)joinPoint.getArgs()[0];
        if(cache.containsKey(num)) {
            System.out.printf(&quot;CacheAspect: Cache에서 구함[%d]\n&quot;, num);
            return cache.get(num);
        }

        Object result = joinPoint.proceed();
        cache.put(num, result);
        System.out.printf(&quot;CacheAspect: Cache에 추가[%d]\n&quot;, num);
        return result;
    }

}</code></pre><p>Pointcut 설정이 첫 번째 인자가 long인 메서드이므로 기존 Calculator의 factorial 메서드에도 적용된다.</p>
<p>어떤 Aspect가 먼저 적용될지는 스프링 프레임워크나 자바 버전에 따라 달라질 수 있기 때문에 순서를 지정하고 싶다면 @Order 애노테이션을 사용해야한다.</p>
<pre><code>@Aspect
@Order(1)</code></pre><p>숫자가 작은 Aspect가 먼저 적용된다.</p>
<h3 id="around의-pointcut-설정과-pointcut-재사용">Around의 Pointcut 설정과 @Pointcut 재사용</h3>
<p>Pointcut 애노테이션이 아닌 @Around 애노테이션에 execution 명시자를 직접 지정할 수도 있다.</p>
<pre><code>@Around(&quot;execution(public * chap07..*())&quot;)</code></pre><p>다른 클래스에 위치한 @Around 애노테이션에서 같은 Pointcut을 재사용하고 싶다면 @Pointcut이 붙은 메서드를 public으로 지정하고 @Around 애노테이션에 해당 Pointcut의 완전한 클래스 이름을 포함한 메서드 이름을 사용하면 된다.</p>
<p>공통으로 사용되는 Pointcut들은 common 클래스로 빼서 따로 관리하는 것이 편리하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[챕터 6: 빈 라이프사이클과 범위]]></title>
            <link>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-6-%EB%B9%88-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4%EA%B3%BC-%EB%B2%94%EC%9C%84-lrn9ooen</link>
            <guid>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-6-%EB%B9%88-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4%EA%B3%BC-%EB%B2%94%EC%9C%84-lrn9ooen</guid>
            <pubDate>Mon, 02 Oct 2023 08:06:23 GMT</pubDate>
            <description><![CDATA[<h2 id="컨테이너-초기화와-종료">컨테이너 초기화와 종료</h2>
<hr>
<p>스프링 컨테이너의 라이프사이클</p>
<blockquote>
<p>컨테이너 초기화 -&gt; 객체 사용 -&gt; 컨테이너 종료</p>
</blockquote>
<p><a href="https://velog.io/@binary_j/%EB%B9%88-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4">https://velog.io/@binary_j/%EB%B9%88-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4</a></p>
<h2 id="스프링-빈-객체의-라이프사이클">스프링 빈 객체의 라이프사이클</h2>
<hr>
<p>스프링 컨테이너는 빈 객체의 라이프 사이클을 관리한다.</p>
<p>빈 객체의 라이프 사이클은 다음과 같다.</p>
<blockquote>
<p>객체 생성 -&gt; 의존 설정 -&gt; 초기화 -&gt; 소멸</p>
</blockquote>
<h3 id="빈-객체의-초기화와-소멸-스프링-인터페이스">빈 객체의 초기화와 소멸: 스프링 인터페이스</h3>
<ul>
<li>InitializingBean: 빈 객체를 생성한 뒤 초기화 과정이 필요하면 해당 인터페이스를 상속하고 afterPropertiesSet() 메서드를 구현하면 됨</li>
<li>DisposableBean: 빈 객체의 소멸 과정이 필요하면 해당 인터페스를 상속하고 destory() 메서드를 구현하면 됨</li>
</ul>
<p>스프링 컨테이너는 빈 객체 생성을 마무리한 뒤 초기화 메서드를 실행한다. 스프링 컨테이너가 종료되면 소멸 메서드가 실행된다.</p>
<h3 id="빈-객체의-초기화와-소멸-커스텀-메서드">빈 객체의 초기화와 소멸: 커스텀 메서드</h3>
<p>앞서 설명한 InitializingBean 인터페이스와 DisposableBean 인터페이스를 사용하지 않고 스프링 설정에서 직접 메서드를 지정할 수 있다. @Bean 애노테이션의 속성값으로 설정 가능하다.</p>
<pre><code>@Bean(initMethod = &quot;connect&quot;, destroyMethod = &quot;close&quot;)</code></pre><p>initMethod에 초기화 할 때 사용할 메서드 이름을, destroyMethod에 소멸할 때 사용할 메서드 이름을 설정해주면 된다.</p>
<p>초기화 메서드의 경우 그냥 빈 설정 코드에서 직접 실행시켜도 된다. 다만 초기화 메서드가 여러번 사용되지 않도록 주의하자.</p>
<h2 id="빈-객체의-생성과-관리-범위">빈 객체의 생성과 관리 범위</h2>
<hr>
<p>별도로 설정을 하지 않는 경우 스프링 빈은 항상 싱글톤 범위를 갖는다. 동일한 이름을 가진 빈 객체는 동일한 빈 객체를 참고한다. 이 외에 프로토타입 범위의 빈을 설정하는 것도 가능하다.</p>
<pre><code>@Bean
@Scop(&quot;prototype&quot;)</code></pre><p>이렇게 @Scope 애노테이션의 설정값으로 &quot;prototype&quot; 값을 주면 프로토타입 범위로 빈 설정이 가능하다. 프로토타입으로 빈의 범위를 지정하면 빈 객체를 구할 때마다 매번 새로운 객체를 생성한다. 프로토타입 범위의 빈은 컨테이너를 종료한다고 소멸 메서드를 실행하지 않기 때문에 빈 객체의 소멸 처리를 코드에서 직접 해야 한다.</p>
<p>** 참고
프로토타입 빈의 경우 더 이상 사용되지 않을 경우 알아서 garbage-collected 된다고 한다. 그러나 메모리 누수 등의 문제가 발생할 수 있으므로 사용이 완료된 프로토타입 빈을 소멸시킬 필요가 있다. </p>
<p>(출처: <a href="https://stackoverflow.com/questions/50681027/do-spring-prototype-beans-need-to-be-destroyed-manually">https://stackoverflow.com/questions/50681027/do-spring-prototype-beans-need-to-be-destroyed-manually</a>)</p>
<p>** 참고 2
스프링 공식 문서에 따르면 Scope 애노테이션으로 설정 가능한 빈 범위는 다음과 같다.</p>
<ul>
<li>singleton : Scopes a single bean definition to a single object instance per Spring IoC container.</li>
<li>prototype : Scopes a single bean definition to any number of object instances.</li>
<li>request : Scopes a single bean definition to the lifecycle of a single HTTP request.</li>
<li>session : Scopes a single bean definition to the lifecycle of a HTTP Session.</li>
<li>global session : Scopes a single bean definition to the lifecycle of a global HTTP Session.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[챕터 5: 컴포넌트 스캔]]></title>
            <link>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-5-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94</link>
            <guid>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-5-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94</guid>
            <pubDate>Sat, 30 Sep 2023 10:03:51 GMT</pubDate>
            <description><![CDATA[<p>컴포넌트 스캔이란 스프링이 직접 클래스를 검색해서 빈으로 등록해주는 기능이다. 설정 클래스에서 빈으로 등록하지 않아도 원하는 클래스를 빈으로 등록할 수 있다.</p>
<h2 id="component-애노테이션으로-스캔-대상-지정">@Component 애노테이션으로 스캔 대상 지정</h2>
<hr>
<p>@Component 애노테이션을 붙이면 해당 클래스를 스캔 대상으로 표시한다.</p>
<p>이 때 애노테이션에 속성값을 줄 수도 있다.</p>
<pre><code>@Component(&quot;infoPrinter&quot;)</code></pre><p>속성값을 주면 빈으로 등록할 때 사용할 이름을 지정할 수 있다. 속성값을 따로 설정하지 않으면 클래스 이름의 첫 글자를 소문자로 바꾼 이름을 빈 이름으로 사용한다.</p>
<h2 id="componentscan-애노테이션으로-스캔-설정">@ComponentScan 애노테이션으로 스캔 설정</h2>
<hr>
<p>@Component 애노테이션을 붙인 클래스를 스캔해서 스프링 빈으로 등록하려면 설정 클래스에 @ComponentScan 애노테이션을 적용해야 한다.</p>
<pre><code>@ComponentScan(basePackages = {&quot;spring&quot;})</code></pre><p>여기서 basePackages 속성값은 스캔 대상 패키지 목록을 지정할 때 사용되는 속성값이다.</p>
<h2 id="스캔-대상에서-제외하거나-포함하기">스캔 대상에서 제외하거나 포함하기</h2>
<hr>
<p>특정 대상을 스캔 대상에서 제외하고 싶다면 excludeFilters 속성을 사용할 수 있다.</p>
<p>excludFilters 속성은 다음과 같이 지정할 수 있다.</p>
<pre><code>@ComponentScan(basePackages = {&quot;spring&quot;},
    excludeFilters = @Filter(type = FilterType.REGEX, 
        pattern = &quot;spring\\..*Dao&quot;))</code></pre><p>excludeFilters 안에서 @Filter 애노테이션의 type 속성값으로 정규표현식을 지정했다. 위의 코드를 적용하면 spring으로 시작하여 Dao로 끝나는 클래스는 컴포넌트 스캔 대상에서 제외된다.</p>
<p>이 외에도 특정 애노테이션을 붙인 타입을 컴포넌트 대상에서 제외할 수도 있다.</p>
<pre><code>@ComponentScan(basePacakges = {&quot;spring&quot;, &quot;spring2&quot;},
    excludeFilters = @Filter(type = FilterType.ANNOTATION,
        classes = {NoProduct.class, ManualBean.class}))</code></pre><p>특정 타입이나 그 하위 타입을 제외하려면 ASSIGNABLE_TYPE을 FilterType으로 사용하면 된다.</p>
<pre><code>@ComponentScan(basePacakges = {&quot;spring&quot;},
    excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE,
        classes = MemberDao.class))</code></pre><p>필터를 여러 개 지정하는 것도 가능하다.</p>
<pre><code>@ComponentScan(basePacakges = {&quot;spring&quot;},
    excludeFilters = {
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MemberDao.class),
        @Filter(type = FilterType.REGEX, pattern = &quot;spring\\..*Dao&quot;)
})</code></pre><p>찾아보니 FilterType.CUSTOM을 통해 제외하고 싶은 패턴을 직접 지정할 수도 있었다.</p>
<p><a href="https://www.baeldung.com/spring-componentscan-filter-type">https://www.baeldung.com/spring-componentscan-filter-type</a></p>
<h2 id="스프링의-기본-스캔-대상">스프링의 기본 스캔 대상</h2>
<hr>
<p>@Componenet 애노테이션을 붙인 클래스만 컴포넌트 스캔 대상에 포함되는 것은 아니다. 다음 애노테이션을 붙인 클래스는 모두 컴포넌트 스캔 대상에 포함된다.</p>
<ul>
<li>@Component</li>
<li>@Controller</li>
<li>@Service</li>
<li>@Repository</li>
<li>@Aspect</li>
<li>@Configuration</li>
</ul>
<p>여기서 @Aspect를 제외한 나머지 애노테이션은 @Component 애노테이션에 몇 가지 기능이 추가된 특수 애노테이션이다.</p>
<h2 id="컴포넌트-스캔에-따른-충돌-처리">컴포넌트 스캔에 따른 충돌 처리</h2>
<hr>
<p>컴포넌트 스캔을 통해 자동으로 빈을 등록할 때 충돌이 발생할 수 있다.</p>
<p>먼저 동일한 빈 이름을 사용하여 빈 이름 충돌이 발생하는 경우 BeanDefinitionStoreException 익셉션이 발생한다. 이를 해결하기 위해 속성값을 사용하여 명시적으로 빈 이름을 서로 다르게 설정해 줄 수 있다.</p>
<p>다음으로 수동 등록한 빈과 충돌이 발생할 수 있다. 스캔할 때 사용하는 빈 이름과 수동 등록한 빈 이름이 같은 경우 수동 등록한 빈이 우선된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[챕터 4: 의존 자동 주입]]></title>
            <link>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-4-%EC%9D%98%EC%A1%B4-%EC%9E%90%EB%8F%99-%EC%A3%BC%EC%9E%85</link>
            <guid>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-4-%EC%9D%98%EC%A1%B4-%EC%9E%90%EB%8F%99-%EC%A3%BC%EC%9E%85</guid>
            <pubDate>Sat, 30 Sep 2023 09:35:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>자동 주입: 스프링이 자동으로 의존하는 빈 객체를 주입해주는 것</p>
</blockquote>
<p>자동 주입을 사용하려면 @Autowired 애노테이션 혹은 @Resource 애노테이션을 사용하면 된다.</p>
<h2 id="autowired-애노테이션을-이용한-의존-자동-주입">@Autowired 애노테이션을 이용한 의존 자동 주입</h2>
<hr>
<p>자동 주입 기능을 사용하려면 의존을 주입할 대상에 @Autowired 애노테이션을 붙이기만 하면 된다.
@Autowired 애노테이션을 붙이면 설정 클래스에서 의존을 주입하지 않아도 된다. 스프링이 알아서 해당 타입의 빈 객체를 찾아서 필드에 할당한다.</p>
<p>찾아보니 이를 다른 말로 필드 주입이라고도 부르는 것 같다.
@Autowired를 사용할 때, 필드에 적절한 빈이 주입하지 않으면 NullPointerException이 발생할 수 있다. 따라서 가능하면 생성자 주입을 사용하자.</p>
<p>@Autowired 애노테이션은 메서드에도 붙일 수 있다. 빈 객체의 메서드에 @Autowired를 붙이면 스프링은 해당 메서드를 호출하며 메서드 파라미터 타입에 해당하는 빈 객체를 찾아 인자로 주입한다.</p>
<p>@Autowired를 사용할 때 해당 타입의 빈이 없으면 적용할 수 없는 빈이 있다는 에러 메시지가 출력된다. 반대로 같은 타입의 빈이 여러개 있다면 해당 타입의 빈을 한정할 수 없다는 에러 메시지가 출력된다. @Autowired를 사용하려면 해당 타입을 가진 빈이 어떤 빈인지 정확하게 한정할 수 있어야 한다.</p>
<h2 id="qualifier-애노테이션을-이용한-의존-객체-선택">@Qualifier 애노테이션을 이용한 의존 객체 선택</h2>
<hr>
<p>위에서 본 것처럼 자동 주입 가능한 빈이 여러개일 경우 어떤 빈을 주입할 지 지정할 수 있는 방법이 필요하다.
이 때 사용되는 것이 @Qualifier 애노테이션이다.</p>
<p>@Qualifier 애노테이션을 사용할 수 있는 위치</p>
<ul>
<li>@Bean 애노테이션을 붙인 빈 설정 메서드</li>
<li>@Autowired 애노테이션을 사용하는 곳</li>
</ul>
<p>사용 예시</p>
<pre><code>@Autowired
@Qualifier(&quot;printer&quot;)
public void setMemberPrinter(MemberPrinter printer) {
    this.printer = printer;
}</code></pre><p>이렇게 하면 한정값이 &quot;printer&quot;인 빈을 의존 주입 후보로 사용한다.</p>
<p>한정자가 없는 경우 기본적으로 빈의 이름이나 파라미터 이름을 한정자로 사용한다.</p>
<h2 id="상위하위-타입-관계와-자동-주입">상위/하위 타입 관계와 자동 주입</h2>
<hr>
<p>MemberPrinter라는 클래스와 이를 상속하는 MemberSummaryPrinter라는 클래스가 있다고 가정해보자. MemberPrinter 타입의 빈과 MemberPrinter 타입의 빈을 @Qualifier 없이 자동 주입 한다면 정상적으로 작동할까?</p>
<p>아니다. 앞에서 동일한 타입의 빈이 여러 개 있으면 빈을 한정할 수 없다는 에러 메시지가 출력된다고 했는데, 이 경우에도 동일한 에러가 발생할 것이다. 왜냐하면 MemberSummaryPrinter 클래스를 MemberPrinter에도 할당할 수 있기 때문이다.
이를 해결하기 위해서는 @Qualifier 애노테이션을 적절히 활용하거나, MemberSummaryPrinter로 자동 주입하여야 한다. 이 경우에 MemberSummaryPrinter 타입의 빈은 하나밖에 없기 때문이다.</p>
<h2 id="autowired-애노테션의-필수-여부">@Autowired 애노테션의 필수 여부</h2>
<hr>
<p>기본적으로 @Autowired 애노테이션은 해당 애노테이션을 붙인 타입에 해당하는 빈이 존재하지 않으면 익셉션을 발생시킨다. 하지만 빈이 반드시 존재할 필요가 없는 경우도 있을 수 있다. 이런 경우에는 어떻게 해야할까?</p>
<p>@Autowired 애노테이션에는 required 라는 속성이 있다. 이 required 속성값을 false로 해주면 매칭되는 빈이 없어도 익셉션이 발생하지 않는다.</p>
<pre><code>@Autowired(required = false)</code></pre><p>또는 자바 8 이상의 버전에서는 Optional을 인자로 전달하는 방법도 있다.</p>
<pre><code>@Autowired
public void setDateFormatter(Optional&lt;DateTimeFormatter&gt; formatterOpt) {
    // do something
}</code></pre><p>이 외에도 @Nullable 애노테이션을 사용하는 방법도 있다.</p>
<pre><code>@Autowired
public void setDateFormatter(@Nullable DateTimeFormatter dateTimeFormatter) {
    // do something
}</code></pre><p>@Nullable과 required=false의 차이</p>
<p>required=false의 경우 주입 대상 빈이 없는 경우 아예 세터메서드를 호출하지 않지만, Nullable의 경우 주입 대상 빈이 존재하지 않으면 세터메서드를 호출하고 null 값을 전달한다.</p>
<h2 id="자동-주입과-명시적-의존-주입-간의-관계">자동 주입과 명시적 의존 주입 간의 관계</h2>
<hr>
<p>설정 클래스에서 의존을 주입했는데 자동 주입 대상인 경우 자동 주입이 우선시된다.</p>
<p>자동 주입과 수동 주입을 섞어 사용하면 주입이 제대로 되지 않아 NullPointerException 이 발생할 수 있으니 일관된 주입 방식을 사용하도록 하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[생성자 주입 vs setter 주입 vs 필드 주입]]></title>
            <link>https://velog.io/@binary_j/%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85-vs-setter-%EC%A3%BC%EC%9E%85-vs-%ED%95%84%EB%93%9C-%EC%A3%BC%EC%9E%85</link>
            <guid>https://velog.io/@binary_j/%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85-vs-setter-%EC%A3%BC%EC%9E%85-vs-%ED%95%84%EB%93%9C-%EC%A3%BC%EC%9E%85</guid>
            <pubDate>Sat, 30 Sep 2023 07:56:35 GMT</pubDate>
            <description><![CDATA[<h2 id="생성자-주입">생성자 주입</h2>
<hr>
<h2 id="setter-주입">setter 주입</h2>
<hr>
<h2 id="필드-주입">필드 주입</h2>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[챕터 3: 스프링 DI]]></title>
            <link>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-3-%EC%8A%A4%ED%94%84%EB%A7%81-DI</link>
            <guid>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-3-%EC%8A%A4%ED%94%84%EB%A7%81-DI</guid>
            <pubDate>Thu, 28 Sep 2023 09:50:36 GMT</pubDate>
            <description><![CDATA[<h2 id="의존이란">의존이란?</h2>
<hr>
<p>한 클래스가 다른 클래스의 메서드를 실행할 떄 이를 &#39;의존&#39;한다고 표현
의존 객체를 구하는 여러 방법이 존재하는데, 스프링과 관련된 것이 DI(의존 주입)이다.</p>
<h3 id="di를-통한-의존-처리">DI를 통한 의존 처리</h3>
<p>DI는 의존하는 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식을 사용한다.</p>
<p>MemberRegisterService라는 클래스에서 MemberDao 클래스의 메서드를 사용한다고 하자. MemberDao 객체를 어떻게 받아올 수 있을까?</p>
<p>직접 객체를 생성하는 방법</p>
<pre><code>private MemberDao memberDao = new MemberDao();</code></pre><p>생성자를 통해 의존 객체를 전달받는 방법</p>
<pre><code>public MemberRegisterService(MemberDao memberDao) {
    this.memberDao = memberDao;
}</code></pre><p>이제 MemberRegisterService 객체를 생성하려면 생성자에 MemberDao 객체를 전달해야만 한다.</p>
<pre><code>MemberDao dao = new MemberDao();
MemberRegisterService svc = new MemberRegisterService(dao);</code></pre><blockquote>
<p>의존 객체를 생성자를 통해 주입한다.</p>
</blockquote>
<h3 id="di와-의존-객체-변경의-유연함">DI와 의존 객체 변경의 유연함</h3>
<p>의존 객체를 직접 생성하면 의존 객체에 변경이 있을 때마다 관련 있는 모든 소스를 수정해야 하지만, DI를 적용하면 다음과 같이 한 군데만 수정하는 것으로 해결된다. 의존 객체를 직접 생성하는 방식에 비해 변경할 코드가 한 곳으로 집중되기 때문에 유지 보수에 편리하다.</p>
<p>변경 전</p>
<pre><code>MemberDao memberDao = new MemberDao();
MemberRegisterService RegSvc = new MemberRegisterService(memberDao);
ChangePasswordService pwdSvc = new ChangePasswordService(memberDao);</code></pre><p>MemberDao 클래스에 캐시를 적용하기 위해 MemberDao 클래스를 상속받는 CachedMemberDao 클래스를 생성하고 이를 적용하고자 한다고 하자.</p>
<p>변경 후</p>
<pre><code>MemberDao memberDao = new CachedMemberDao();
MemberRegisterService RegSvc = new MemberRegisterService(memberDao);
ChangePasswordService pwdSvc = new ChangePasswordService(memberDao);</code></pre><h3 id="객체-조립기">객체 조립기</h3>
<p>책에 있는 예제 코드를 굳이 여기에 적고 설명하진 않을 예정이다.
앞서 교재에서 간단한 회원가입 프로젝트를 작성하였다.</p>
<p>객체들을 생성하고 의존 객체를 주입해주는 클래스를 별도로 작성할 수 있는데 서로 다른 객체들을 조립해준다는 의미에서 객체 조립기(assembler)라고 표현한다.</p>
<p>객체 조립기 Assembler 클래스를 사용하는 코드는 Assembler 객체를 생성한 후에 get 메서드를 사용하여 필요한 객체를 구하고 그 객체를 사용한다.</p>
<h3 id="스프링의-di-설정">스프링의 DI 설정</h3>
<p>스프링은 앞서 설명한 객체 조립기와 비슷한 기능을 제공한다. 스프링은 필요한 객체를 생성하고 생성한 객체에 의존을 주입하고 조립기의 getter처럼 객체를 제공하는 기능을 정리하고 있다. </p>
<p>조립기 대신 스프링을 사용하려면 스프링이 어떤 객체를 생성하고, 의존을 어떻게 주입할 지 정의하고 있는 설정 정보를 작성해야 한다. 설정 코드는 다음과 같다.</p>
<pre><code>package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberRegisterService;

@Configuration
public class AppCtx {

    @Bean
    public MemberDao memberDao() {
        return new MemberDao();
    }

    @Bean
    public MemberRegisterService memberRegSvc() {
        return new MemberRegisterService(memberDao());
    }

    @Bean
    public ChangePasswordService changePwdSvc() {
        ChangePasswordService pwdSvc = new ChangePasswordService();
        pwdSvc.setMemberDao(memberDao());
        return pwdSvc;
    }

}</code></pre><p>앞서 설명했듯 @Configuration 애노테이션은 스프링 설정 클래스를 의미한다.</p>
<p>소스를 보면 알 수 있듯이 MemberRegisterService는 생성자를 통해 의존을 주입하고 있으며, ChangePasswordService는 세터를 통해 의존을 주입하고 있다.</p>
<p>실제로 객체를 생성하고 의존 객체를 주입하는 것은 스프링 컨테이너이므로 설정 클래스를 다 만들었으면 이를 통해 컨테이너를 생성해야 한다.</p>
<pre><code>ApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);</code></pre><p>컨테이너를 생성했으면 getBean()을 통해 객체를 구할 수 있다.</p>
<pre><code>MemberRegisterService regSvc = ctx.getBean(&quot;memberRegSvc&quot;, MemberRegisterService.class);</code></pre><h3 id="di-주입-방식-생성자-vs-세터-메서드">DI 주입 방식: 생성자 vs 세터 메서드</h3>
<p>책에서는 둘 중 하나를 더 낫다고 할 수 없고 적절하게 섞어 사용하면 된다고 서술하고 있다. 실제로 그런지 구글링을 해보았다.</p>
<p>스프링 공식 문서에 의하면 가능하면 생성자 주입을 사용하는 것이 바람직하다고 한다. 생성자 주입 방식을 사용하면 순환 참조를 방지할 수 있고, 불변성이 보장되게 할 수 있으며(final을 붙임), nullPointerException이 방지되고, 테스트가 편리하다는 이점이 있다. 이에 대해서는 다음 글에서 더 자세히 다루도록 하겠다.</p>
<h3 id="configuration-설정-클래스의-bean-설정과-싱글톤">@Configuration 설정 클래스의 @Bean 설정과 싱글톤</h3>
<p>스프링 컨테이너는 @Bean이 붙은 메서드에 대해 한 개의 객체만을 생성한다고 앞서 말했다. 설정 메서드에서 memberDao()를 몇 번을 호출하더라도 항상 같은 객체를 리턴한다는 말이다. 스프링은 설정 클래스를 그대로 사용하지 않고, 설정 클래스를 상속한 새로운 설정 클래스를 만들어서 사용한다.</p>
<p>스프링이 런타임에 생성한 설정 클래스는 해당 빈이 이미 존재하는지 확인하고 존재하지 않을 경우에만 새로운 빈 객체를 생성한다. 이후 동일한 객체를 다시 호출하면 미리 생성해둔 빈 객체를 보관해뒀다가 리턴한다.</p>
<h3 id="두-개-이상의-설정-파일-사용하기">두 개 이상의 설정 파일 사용하기</h3>
<p>스프링은 한 개 이상의 설정 파일을 사용해서 컨테이너를 생성하도록 허용한다. 설정하는 빈의 개수가 증가하면 영역별로 설정 파일을 나누어 관리하는 것이 더 편리할 수 있다. 이 때 @Autowired라는 애노테이션을 사용할 수 있다.</p>
<p>AppConf1.java에서 다음과 같이 MemberDao, MemberPrinter 타입의 빈을 설정해놓고 이를 AppConf2.java에서 @Autowired 애노테이션을 사용하여 해당 타입의 빈을 찾아서 필드에 할당해줄 수 있다.</p>
<p>AppConf1.java</p>
<pre><code>package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.MemberDao;
import spring.MemberPrinter;

@Configuration
public class AppConf1 {

    @Bean
    public MemberDao memberDao() {
        return new MemberDao();
    }

    @Bean
    public MemberPrinter memberPrinter() {
        return new MemberPrinter();
    }

}</code></pre><p>AppConf2.java</p>
<pre><code>package config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberInfoPrinter;
import spring.MemberListPrinter;
import spring.MemberPrinter;
import spring.MemberRegisterService;
import spring.VersionPrinter;

@Configuration
public class AppConf2 {
    @Autowired
    private MemberDao memberDao;
    @Autowired
    private MemberPrinter memberPrinter;

    @Bean
    public MemberRegisterService memberRegSvc() {
        return new MemberRegisterService(memberDao);
    }

    @Bean
    public ChangePasswordService changePwdSvc() {
        ChangePasswordService pwdSvc = new ChangePasswordService();
        pwdSvc.setMemberDao(memberDao);
        return pwdSvc;
    }

    @Bean
    public MemberListPrinter listPrinter() {
        return new MemberListPrinter(memberDao, memberPrinter);
    }

    @Bean
    public MemberInfoPrinter infoPrinter() {
        MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
        infoPrinter.setMemberDao(memberDao);
        infoPrinter.setPrinter(memberPrinter);
        return infoPrinter;
    }

    @Bean
    public VersionPrinter versionPrinter() {
        VersionPrinter versionPrinter = new VersionPrinter();
        versionPrinter.setMajorVersion(5);
        versionPrinter.setMinorVersion(0);
        return versionPrinter;
    }
}</code></pre><p>스프링 컨테이너를 생성할 때에는 그냥 설정 파일 두개를 모두 전달해 주면 된다.</p>
<pre><code>ctx = new AnnotationConfigApplicationContext(AppConf1.class, AppConf2.class);</code></pre><p>두 개 이상의 설정 파일을 사용하는 또 다른 방법이 있다. @Import 애노테이션을 사용하는 것이다.</p>
<pre><code>package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import spring.MemberDao;
import spring.MemberPrinter;

@Configuration
@Import({AppConf2.class})
public class AppConfImport {

    @Bean
    public MemberDao memberDao() {
        return new MemberDao();
    }

    @Bean
    public MemberPrinter memberPrinter() {
        return new MemberPrinter();
    }
}</code></pre><p>이렇게 작성하면 @Import 애노테이션으로 지정한 AppConf2 클래스도 함께 사용되기 때문에 컨테이너를 생성할 때 굳이 지정해 줄 필요가 없다.
배열 형태로 여러개의 설정 파일을 import 할 수도 있다.</p>
<pre><code>@Import({AppConf1.class, AppConf2.class});</code></pre><h3 id="getbean-메서드-사용">getBean 메서드 사용</h3>
<p>앞서 사용한 getBean 메서드는 첫 번째 인자가 빈의 이름, 두 번째 인자가 빈의 타입이기 때문에 getBean을 호출할 때 존재하지 않는 빈의 이름을 사용하거나 잘못된 타입을 전달하면 익셉션이 발생한다.</p>
<p>빈 이름을 지정하지 않고 타입만으로 빈을 구할수도 있지만, 같은 타입의 빈이 여러개 존재할 경우 마찬가지로 익셉션이 발생한다.</p>
<h3 id="주입-대상-객체의-설정">주입 대상 객체의 설정</h3>
<p>의존 주입을 할 때 주입 대상 객체가 항상 빈 객체일 필요는 없다. 빈으로 등록되지 않은 일반 객체도 생성해서 주입할 수 있다.</p>
<p>객체를 스프링 빈으로 등록하지 않은 경우 스프링 컨테이너는 해당 객체를 관리하지 않는다. getBean() 메서드로 해당 객체를 구할 수 없으며 이 외의 스프링의 여러 객체 관리 기능도 사용할 수 없다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[챕터 2: 스프링 시작하기]]></title>
            <link>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-2</link>
            <guid>https://velog.io/@binary_j/%EC%B1%95%ED%84%B0-2</guid>
            <pubDate>Thu, 28 Sep 2023 08:36:31 GMT</pubDate>
            <description><![CDATA[<h2 id="스프링-시작하기">스프링 시작하기</h2>
<hr>
<p>메이븐과 그레이들 두 가지를 모두 소개하고 있지만 책의 뒷부분은 거의 메이븐으로 되어있는 것 같아 나도 메이븐으로 진행하였다.
찾아보니 메이븐보다 그레이들이 훨씬 유지보수도 쉽고 성능도 좋다고 한다.. 다음에는 그레이들을 사용해 봐야겠다.</p>
<h3 id="루트-폴더에-pomxml-작성">루트 폴더에 pom.xml 작성</h3>
<p>pom.xml은 메이븐 프로젝트에 대한 설정 정보를 관리하는 파일이다.</p>
<p>소스를 다 적기엔 너무 길기 때문에 주요한 내용만 정리해보도록 하겠다.</p>
<pre><code>&lt;artifactId&gt;sp5-chap02&lt;artifactId&gt;</code></pre><p>프로젝트의 식별자 지정</p>
<pre><code>&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-context&lt;/artifactId&gt;
        &lt;version&gt;5.1.6.RELEASE&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</code></pre><p>dependencies는 의존 관리를 위한 설정 정보를 작성하는 곳이다.
의존을 추가한다는 것은 자바 어플리케이션 클래스 패스에 spring-context 모듈을 추가한다는 것을 의미한다. 메이븐은 코드를 컴파일하거나 실행할 때 메이븐 로컬 리포지토리에 dependency로 설정한 아티팩트 파일이 있는지 검사하고, 있을 경우 해당 파일을 사용하고 없을 경우에는 메이븐 중앙 원격 리포지토리에서 해당 파일을 다운로드하여 로컬 리포지토리에 저장 후 사용한다.</p>
<p>프로젝트에서 사용할 spring-context 모듈의 버전을 설정해 주었다.
책이랑 동일한 버전으로 작성하니 버전 문제가 조금 있어서 나는 5.1.6 RELEASE를 사용하였다.</p>
<p>cf. 의존 전이
메이븐 프로젝트를 컴파일 해보면 내가 dependency에 추가한 아티팩트 파일 말고도 온갖 파일이 우수수 다운로도 되는걸 볼 수 있다.
왜 이럴까?
내가 다운로드한 파일이 의존하는 파일들을 또 다운로드하고 그 파일이 의존하는 파일들을 또 다운로드하고 ... 이렇게 되기 때문이다.
이걸 의존 전이라고 부른다.</p>
<pre><code>&lt;plugin&gt;
    &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
    &lt;version&gt;3.8.1&lt;/version&gt;
    &lt;configuration&gt;
        &lt;source&gt;${java.verion}&lt;/source&gt;
        &lt;target&gt;${java.verion}&lt;&lt;/target&gt;
        &lt;encoding&gt;utf-8&lt;/encoding&gt;
    &lt;/configuration&gt;
&lt;/plugin&gt;</code></pre><p>${java.version} 기준으로 자바 소스 컴파일
마찬가지로 버전 문제 때문에 나는 메이븐 플러그인 3.7.0 대신 3.8.1을 사용했다.</p>
<p>pom.xml과 기본 폴더 구조를 생성했으면 이클립스에서 메이븐 프로젝트로 임포트 하면 된다.</p>
<ul>
<li>src/main/java 소스 폴더로 정의되어 있음</li>
<li>Maven Dependencies 메이븐 의존에 설정한 아티팩트가 이클립스 프로젝트의 클래스패스에 추가됨</li>
</ul>
<h3 id="예제-코드-작성">예제 코드 작성</h3>
<p>Greeter.java</p>
<pre><code>package chap02;

public class Greeter {
    private String format;

    public String greet(String guest) {
        return String.format(format, guest);
    }

    public void setFormat(String format) {
        this.format = format;
    }

}</code></pre><p>AppContext.java</p>
<pre><code>package chap02;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppContext {

    @Bean
    public Greeter greeter() {
        Greeter g = new Greeter();
        g.setFormat(&quot;%s, 안녕하세요!&quot;);
        return g;
    }

}</code></pre><ul>
<li>@Configuration 이 애노테이션이 붙으면 스프링은 해당 클래스를 스프링 설정 클래스로 인식한다.</li>
<li>@Bean 스프링이 생성하는 객체를 Bean 객체라고 부른다. 이 애노테이션이 붙으면 해당 메서드가 생성한 객체를 스프링이 관리하는 빈 객체로 등록한다.</li>
</ul>
<p>Main.java</p>
<pre><code>package chap02;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = 
                new AnnotationConfigApplicationContext(AppContext.class);
        Greeter g = ctx.getBean(&quot;greeter&quot;, Greeter.class);
        String msg = g.greet(&quot;스프링&quot;);
        System.out.println(msg);
        ctx.close();
    }
}</code></pre><p>AnnotationConfigApplicationContext 자바 설정에서 정보 읽어와 빈 객체를 생성하고 관리함
AnnotationConfigApplicationContext 객체를 생성할 때 AppContext 클래스를 생성자 파라미터로 전달한다. 이때 @Bean 설정 정보를 읽어와 Greeter 객체를 생성하고 초기화한다.
getBean() 메서드는 AnnotationConfigApplicationContext가 자바 설정을 읽어와 생성한 빈 객체를 검색할 때 사용된다. getBean() 메서드의 첫 번째 파라미터는 빈 객체의 이름, 두 번쨰 파라미터는 검색할 빈 객체의 타입이다.</p>
<h2 id="스프링은-객체-컨테이너">스프링은 객체 컨테이너</h2>
<hr>
<p>스프링의 핵심 기능은 객체를 생성하고 초기화 하는 것
이와 관련된 기능은 ApplicationContext라는 인터페이스에 정의되어 있으며 AnnotationConfigApplicationContext 클래스는 이 인터페이스를 알맞게 구현한 클래스 중 하나다.</p>
<p>ApplicationContext(또는 BeanFactory)는 빈 객체의 생성, 초기화, 보관, 제거 등을 관리하고 있어 스프링 컨테이너라고도 불린다.</p>
<h3 id="싱글톤-객체">싱글톤 객체</h3>
<p>스프링은 기본적으로 하나의 @Bean 애노테이션에 대해 한 개의 빈 객체만을 생성한다. 이때 빈 객체는 싱글톤 범위를 갖는다고 표현한다. 따라서 다음과 같이 코드를 작성하여도, g1과 g2는 같은 객체임을 확인할 수 있다.</p>
<pre><code>package chap02;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main2 {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = 
                new AnnotationConfigApplicationContext(AppContext.class);
        Greeter g = ctx.getBean(&quot;greeter&quot;, Greeter.class);
        String msg = g.greet(&quot;스프링&quot;);
        System.out.println(msg);
        ctx.close();
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[챕터 1: 들어가며]]></title>
            <link>https://velog.io/@binary_j/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D5-%EC%B1%95%ED%84%B0-1</link>
            <guid>https://velog.io/@binary_j/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D5-%EC%B1%95%ED%84%B0-1</guid>
            <pubDate>Thu, 28 Sep 2023 07:08:41 GMT</pubDate>
            <description><![CDATA[<h2 id="스프링이란">스프링이란</h2>
<hr>
<p>스프링 프레임워크의 4가지 주요 특징</p>
<ul>
<li>의존 주입(DI) 지원</li>
<li>AOP 지원</li>
<li>MVC 웹 프레임워크 지원</li>
<li>JDBC, JPA 연동, 선언적 트랜잭션 처리 등 DB 연동 지원</li>
</ul>
<p>스프링 관련 프로젝트</p>
<ul>
<li>스프링 데이터</li>
<li>스프링 시큐리티</li>
<li>스프링 배치</li>
</ul>
<p>-&gt; 책에 나와있는 예시들 아직도 사용되는지 궁금해져서 찾아봤다. 현재(2023년 9월) 기준 셋 다 사용되는데 스프링 배치는 경우에 따라 다른걸로 대체할 때도 많은듯</p>
<h2 id="환경설정">환경설정</h2>
<hr>
<p><a href="https://velog.io/@binary_j/%EC%B4%88%EB%B3%B4-%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-Cht.1">https://velog.io/@binary_j/%EC%B4%88%EB%B3%B4-%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-Cht.1</a></p>
<p>이 책은 스프링 5를 기준으로 하는데.. 현재 최신버전은 6.0.12다.
찾아보니 스프링 5는 자바 8 이상, 스프링 6은 Java 17 and Jakarta EE 9 이상을 요구한다고 한다.
내 컴에 깔린 JDK는 11이기때문에 스프링 5 버전 그대로 진행했다.</p>
<p>참고
현재(23.09.28) 기준 가장 최신 버전</p>
<ul>
<li>스프링 6</li>
<li>자바 Java 21 or JDK 21 </li>
<li>메이븐 3.9.4</li>
<li>그레이들 8.3</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 정규식(lookahead, lookbehind)]]></title>
            <link>https://velog.io/@binary_j/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%A0%95%EA%B7%9C%EC%8B%9Dlookahead-lookbehind</link>
            <guid>https://velog.io/@binary_j/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%A0%95%EA%B7%9C%EC%8B%9Dlookahead-lookbehind</guid>
            <pubDate>Mon, 12 Dec 2022 14:33:53 GMT</pubDate>
            <description><![CDATA[<p>하... 이번에는 정말 실수 없이 개발했다고 생각했는데 또 몰랐던 내용을 알게 되었다. 언제쯤 바보같은 실수를 안 하게 될지..
안드로이드에서는 잘 동작하던 소스가 iOS에서는 동작하지 않았다.
알고 보니 잘못된 정규식 때문이었다.
이 기회에 lookahead 정규식과 lookbehind 정규식을 정리해 보려고 한다.</p>
<hr>
<h3 id="lookahead-정규식">lookahead 정규식</h3>
<ul>
<li><p>positive lookahead :  X(?=Y)
X보다 뒤에 있는 문자열에 Y가 있는 경우 X</p>
</li>
<li><p>negative lookahead :  X(?!Y)
X보다 뒤에 있는 문자열에 Y가 있지 않은 경우 X</p>
</li>
</ul>
<hr>
<h3 id="lookbehind-정규식">lookbehind 정규식</h3>
<ul>
<li><p>positive lookbehind : (?&lt;=Y)X
X보다 앞에 있는 문자열에 Y가 있는 경우 X</p>
</li>
<li><p>negative lookbehind : (?&lt;!Y)X
X보다 앞에 있는 Y가 있지 않은 경우 X</p>
</li>
</ul>
<hr>
<p>바로 이 lookbehind가 문제였다.
사파리나 익스플로러같은 웹 브라우저는 lookbehind 정규식을 지원하지 않는다고 한다. 그래서 lookahead 정규식으로 대체하는 방법으로 문제를 해결하였다.
이 외에도 정규식은 잘못 사용하면 속도가 엄청 느려지기도 하고.. 문제가 발생할 수 있으니 꼭 체크를 하고 사용해야겠다.</p>
<hr>
<p>[정규식 체크할 때 도움이 될 만한 자료들]</p>
<p>정규식 validator
<a href="https://github.com/validatorjs/validator.js">https://github.com/validatorjs/validator.js</a>
<a href="https://express-validator.github.io/docs/">https://express-validator.github.io/docs/</a></p>
<p>정규식 analizer
<a href="https://github.com/davisjam/safe-regex">https://github.com/davisjam/safe-regex</a>
<a href="https://www.cs.bham.ac.uk/~hxt/research/rxxr2/">https://www.cs.bham.ac.uk/~hxt/research/rxxr2/</a></p>
<hr>
<h3 id="reference">Reference</h3>
<ul>
<li><a href="https://mc500.tistory.com/658">https://mc500.tistory.com/658</a></li>
<li><a href="https://blog.hexabrain.net/205">https://blog.hexabrain.net/205</a></li>
<li><a href="https://yceffort.kr/2021/09/deep-dive-javascript-regex#2-validation-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%82%AC%EC%9A%A9">https://yceffort.kr/2021/09/deep-dive-javascript-regex#2-validation-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%82%AC%EC%9A%A9</a></li>
<li><a href="https://dantechblog.gatsbyjs.io/posts/til-regex/">https://dantechblog.gatsbyjs.io/posts/til-regex/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[mixed content 오류]]></title>
            <link>https://velog.io/@binary_j/mixed-content-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@binary_j/mixed-content-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Sun, 04 Dec 2022 09:29:04 GMT</pubDate>
            <description><![CDATA[<p>mixed content: the page at &#39;url&#39; was loaded over https, but requested an insecure xmlhttprequest endpoint &#39;api url&#39;. this request has been blocked; the content must be served over https.</p>
<p>보안이 더 강력한 https 컨텐트에서 http 컨텐트로 요청을 보낼 때 발생할 수 있는 에러이다. 나는 https 페이지에서 http://로 시작하는 API로 요청을 보낼 때 발생했다.</p>
<pre><code>&lt;meta http-equiv=&quot;Content-Security-Policy&quot; content=&quot;upgrade-insecure-requests&quot;&gt;</code></pre><p>index.html의 head에 요청을 업그레이드하도록 설정하니 해결되었다.</p>
<h2 id="reference">Reference</h2>
<p><a href="https://stackoverflow.com/questions/35178135/how-to-fix-insecure-content-was-loaded-over-https-but-requested-an-insecure-re">https://stackoverflow.com/questions/35178135/how-to-fix-insecure-content-was-loaded-over-https-but-requested-an-insecure-re</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1주말 1토이프로젝트: 첫주]]></title>
            <link>https://velog.io/@binary_j/1%EC%A3%BC%EB%A7%90-1%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B2%AB%EC%A3%BC</link>
            <guid>https://velog.io/@binary_j/1%EC%A3%BC%EB%A7%90-1%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B2%AB%EC%A3%BC</guid>
            <pubDate>Sun, 04 Dec 2022 09:21:20 GMT</pubDate>
            <description><![CDATA[<p>Vue.js를 좀 더 잘 다루고 싶어서 매주 주말마다 간단한 토이 프로젝트를 하나씩 해보려고 한다.
얼마 전 &lt;스프링 부트와 aws로 혼자 구현하는 웹 서비스&gt;를 한번 끝까지 진행해 보았다. (책이 조금 오래전에 쓰여져서 지금은 버전이 맞지 않는 부분이 꽤 있다. 관련된 후기도 곧 올릴 계획이다.) 책을 전부 읽은 후 백단은 스프링부트, 프론트는 Vue를 사용해서 쇼핑몰을 만들어 보려고 했는데, Vue.js 지식이 부족해서 너무 시간을 많이 잡아먹는 것 같다는 생각이 들었다. 그래서 당분간은 Vue 연습을 해볼 계획이다.</p>
<p><a href="https://github.com/dl0312/open-apis-korea">https://github.com/dl0312/open-apis-korea</a> 뷰 토이프로젝트를 진행하신 다른 분들의 레포/게시물들을 찾아보다가 번역된 오픈 API 리스트를 발견했다!
그 중 간단한 토이프로젝트용으로 정말 유용할 것 같은 Bored API로 프로젝트를 진행하였다.
Bored API 설명 : <a href="https://www.boredapi.com/">https://www.boredapi.com/</a>
사용방법도 정말 간단하다. 그냥 get으로 요청을 보내면 json으로 할일을 추천해주는 응답이 온다. 여기서 필요한 부분만 떼서 사용하면 된다.
나는 버튼을 누를때마다 axios로 Bored API로 get 요청을 보내고 받아온 결과를 화면에 보여주게 했다.</p>
<p>배포는 Netlify로 진행했다.
어차피 프론트만 존재하는 간단한 프로젝트기 때문에 Netlify로 충분했다.
AWS는 까딱 잘못 설정하면 요금이 나가버리기도 하니 다음에 좀 더 규모가 큰 토이프로젝트를 진행할 때 사용할 계획이다.</p>
<p>Netlify 사용법은 아주 간단하다. 깃허브로 가입해서 배포하려는 레포를 연결하고 권한만 주면 알아서 푸시할 때마다 배포해준다.</p>
<p><a href="https://flourishing-sunburst-8f954d.netlify.app/">https://flourishing-sunburst-8f954d.netlify.app/</a> (결과물)
<img src="https://velog.velcdn.com/images/binary_j/post/7476cc87-23b7-4686-8862-8e89fce1c09e/image.png" alt=""></p>
<p>깃헙 레포지토리
<a href="https://github.com/jooijin/GiveMeSomethingToDo">https://github.com/jooijin/GiveMeSomethingToDo</a></p>
<p>대단한 기능은 없지만 확실히 직접 만들어보는게 빨리 익히는 것 같다.
다음주엔 무슨 주제로 하지..</p>
<h2 id="reference">Reference</h2>
<p><a href="https://github.com/dl0312/open-apis-korea">https://github.com/dl0312/open-apis-korea</a>
<a href="https://www.boredapi.com/">https://www.boredapi.com/</a>
<a href="https://gifer.com/en/Mc28">https://gifer.com/en/Mc28</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[no main manifest attribute 해결(gradle)]]></title>
            <link>https://velog.io/@binary_j/no-main-manifest-attribute-%ED%95%B4%EA%B2%B0gradle</link>
            <guid>https://velog.io/@binary_j/no-main-manifest-attribute-%ED%95%B4%EA%B2%B0gradle</guid>
            <pubDate>Sun, 13 Nov 2022 17:16:40 GMT</pubDate>
            <description><![CDATA[<p>jar 파일을 빌드할 때, excutable-jar와 plain-jar 두 가지 jar 파일이 빌드된다.
excutable-jar는 모든 의존성이 포함된 jar 파일로 바로 실행이 가능한 반면
plain-jar는 의존성이 빠져있기 때문에 실행하면 no main manifest attribute라는 오류 메세지가 뜨게 된다.
(사실 항상 이 이유 때문에 저 오류가 뜨는 것은 아니다. 다른 설정을 잘못해서 main 함수를 못찾는 걸수도 있고.. 다양한 이유가 있겠지만 나의 경우는 plain-jar가 문제였다.)</p>
<p>plain-jar가 생성되지 않게 하기 위해 build.gradle에 다음 설정을 추가해준다.</p>
<pre><code>jar{
    enabled=false
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[스프링부트 토이프로젝트: 마이주얼리]]></title>
            <link>https://velog.io/@binary_j/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%88%EC%9D%B4%EC%A3%BC%EC%96%BC%EB%A6%AC</link>
            <guid>https://velog.io/@binary_j/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%88%EC%9D%B4%EC%A3%BC%EC%96%BC%EB%A6%AC</guid>
            <pubDate>Sun, 21 Aug 2022 04:32:21 GMT</pubDate>
            <description><![CDATA[<p>&lt;스프링부트와 AWS로 혼자 구현하는 웹 서비스&gt;를 보는 중인데 이 책을 참고해서 토이프로젝트를 하나 해보기로 했다.
ㅇㄹ의 의견에 따라 주얼리샵 컨셉으로 진행할 예정이다. 그냥 간단하게 연습할 용으로 쇼핑몰로 했다.
맨날 maven만 썼었는데 이번에는 이 책에서 사용한대로 그레이들을 써볼 생각이고 책에 AWS 인프라에 대한 부분도 설명되어 있는 것 같아서 이 부분까지 한번 다뤄보려고 한다.
마침 집에 사놓고 안보고 있는 AWS 책도 있으니 이참에 같이 보면 좋을 것 같다!
해커톤을 끝내고 한동안 공부를 소홀히 했었는데.. 이러다 올해가 가버릴 것 같아서 끔찍하다.
해커톤 회고록도 더 까먹기 전에 빨리 써야겠다.</p>
<p>GitHub 주소 : <a href="https://github.com/jooijin/MyJewerly">https://github.com/jooijin/MyJewerly</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[static import]]></title>
            <link>https://velog.io/@binary_j/static-import</link>
            <guid>https://velog.io/@binary_j/static-import</guid>
            <pubDate>Sun, 14 Aug 2022 07:30:10 GMT</pubDate>
            <description><![CDATA[<p>import static 으로 import하면 그 클래스의 정적 메소드를 클래스명이나 패키지명 없이 사용할 수 있다.
그러나 동일한 이름의 메소드를 사용하고 있을 때에는 권장되지 않는다.
동일한 이름의 메소드가 존재하면 현재 클래스에서 선언한 메소드가 우선되기 때문에 코드가 예상대로 돌아가지 않을 수 있다.
같은 이유로 * 와일드카드로 메소드 전체를 import 하기보다는 사용하고자 하는 특정 메소드명까지 적어서 import 하는 것을 권장한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[뷰 인스턴스 라이프 사이클]]></title>
            <link>https://velog.io/@binary_j/%EB%B7%B0-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4</link>
            <guid>https://velog.io/@binary_j/%EB%B7%B0-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4</guid>
            <pubDate>Sun, 12 Jun 2022 06:58:37 GMT</pubDate>
            <description><![CDATA[<p>회사에서 매일 Vue.js를 쓰고 있지만 맨날 비슷한 부분만 보다보니 기본적인 부분은 잘 모를 때가 있다.
캡틴판교님의 Vue.js 입문이라는 책을 몇번 읽어보면서 공부중인데 중요한 내용은 따로 정리해두어야겠다는 생각이 들었다.</p>
<h3 id="1-뷰-인스턴스-라이프-사이클이란">1. 뷰 인스턴스 라이프 사이클이란?</h3>
<blockquote>
<p>뷰 인스턴스가 가지는 생명 주기를 뜻한다.</p>
</blockquote>
<p>공식 사이트의 자료에 따르면 뷰 인스턴스의 라이프 사이클은 다음과 같다.
<img src="https://velog.velcdn.com/images/binary_j/post/1d78c486-3fd4-43cd-b0e5-446caa41963f/image.png" alt="">
뷰 인스턴스 라이프 사이클은 크게 보면 생성(new Vue()), 부착(Mount), 갱신(Update), 소멸(Destroy)로 나누어지며 각각의 단계를 더 자세히 살펴보면 beforeCreate, created, beforeMount, mounted, (beforeUpdate, updated), beforeDestroy, destoyed의 8단계로 나눌 수 있다. beforeUpdate와 updated 단계는 데이터의 변경이 이루어지는 경우에만 거치는 단계이기 때문에 괄호 안에 표시하였다.</p>
<h3 id="1-beforecreate">1. beforeCreate</h3>
<p>인스턴스 초기화 후 가장 처음 실행되는 단계
props만 전달받은 상태이며 data()나 computed는 실행되지 않은 상태이다. 
DOM 요소에도 접근이 불가능하다.</p>
<blockquote>
<p>Vue.js 공식 문서에 &quot;after props resolution&quot;라는 말이 있어서 beforeCreate에서 props 접근이 가능한지 궁금해져 추가적으로 조금 더 조사해 봤는데 다음과 같은 답변들을 보았다.
<a href="https://stackoverflow.com/questions/71369581/can-props-be-accessed-in-beforecreated-of-vue">https://stackoverflow.com/questions/71369581/can-props-be-accessed-in-beforecreated-of-vue</a>
<a href="https://forum.vuejs.org/t/accessing-props-in-beforecreate/37892">https://forum.vuejs.org/t/accessing-props-in-beforecreate/37892</a>
아마도 Vue.js 3에서만 가능한 것 같고, props에서 전달받은 값을 사용하고 싶다면 이후 사용하는게 더 좋을 것 같다.</p>
</blockquote>
<h3 id="2-created">2. created</h3>
<p>반응형 데이터, computed, methods, watchers가 정의된 후 실행되는 단계이다. 그러나 Mount가 일어나기 전이기 때문에 template이나 el 속성에는 접근할 수 없다.</p>
<h3 id="3-beforemount">3. beforeMount</h3>
<p>DOM render()가 일어나기 직전의 단계이다.</p>
<h3 id="4-mounted">4. mounted</h3>
<p>컴포넌트가 부착된 직후의 단계이다. 동기적인 자식 컴포넌트 모두에 접근 가능하며 DOM 요소에도 접근 가능하다. 그러나 비동기적인 컴포넌트와 &lt;Suspense&gt; 트리 안의 컴포넌트에는 접근이 불가능하다.
DOM 요소 접근이 가능하므로 화면 요소를 제어하는 로직을 수행하기 좋다.</p>
<h3 id="5-beforeupdate">5. beforeUpdate</h3>
<p>컴포넌트가 DOM tree를 변경하기 직전의 단계이다. DOM이 업데이트 되기 전 DOM 요소에 접근하여 수행해야할 작업이 있다면 이때 수행하면 된다.</p>
<h3 id="6-updated">6. updated</h3>
<p>컴포넌트의 DOM tree가 업데이트 된 직후의 단계이다. 여기서 컴포넌트의 상태를 변경하면 무한 업데이트 루프에 빠질 수 있기때문에 변경하지 않는 것이 좋다. 또한 특정 상태가 바뀐 후의 DOM 요소에 접근하고 싶다면 nextTick()이라는 메서드를 사용할 수 있다.</p>
<h3 id="7-beforedestroy">7. beforeDestroy</h3>
<p>뷰 인스턴스가 파괴되기 직전의 단계이다. 아직까지는 컴포넌트의 모든 부분이 남아있기 때문에 모든 요소에 접근 가능하다. 인스턴스 파괴 전 이벤트나 데이터를 삭제하고 싶다면 이 단계에서 수행하면 된다.</p>
<h3 id="8-destroyed">8. destroyed</h3>
<p>뷰 인스턴스가 파괴된 후의 단계이다. 인스턴스가 파괴되었기 때문에 당연히 어떤 요소에도 접근이 불가능하다.</p>
<h3 id="reference">Reference</h3>
<ul>
<li>Vue.js 입문</li>
<li>Vue.js 공식 사이트의 라이프 사이클 훅 (<a href="https://v2.vuejs.org/v2/guide/instance.html#Instance-Lifecycle-Hooks">https://v2.vuejs.org/v2/guide/instance.html#Instance-Lifecycle-Hooks</a>, <a href="https://vuejs.org/api/options-lifecycle.html">https://vuejs.org/api/options-lifecycle.html</a>)</li>
<li><a href="https://stackoverflow.com/questions/71369581/can-props-be-accessed-in-beforecreated-of-vue">https://stackoverflow.com/questions/71369581/can-props-be-accessed-in-beforecreated-of-vue</a></li>
<li><a href="https://forum.vuejs.org/t/accessing-props-in-beforecreate/37892">https://forum.vuejs.org/t/accessing-props-in-beforecreate/37892</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT 토큰]]></title>
            <link>https://velog.io/@binary_j/JWT-%ED%86%A0%ED%81%B0</link>
            <guid>https://velog.io/@binary_j/JWT-%ED%86%A0%ED%81%B0</guid>
            <pubDate>Sun, 12 Jun 2022 04:23:39 GMT</pubDate>
            <description><![CDATA[<p>회사에서 하고 있는 미니프로젝트에서 JWT 토큰을 사용할 일이 있는데,
사실 토큰이라는 것만 알지 제대로 알지 못해서 한번 정리해 보기로 했다.</p>
<h3 id="1-jwt란">1. JWT란?</h3>
<blockquote>
<p>JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. </p>
</blockquote>
<p>공식 문서에 따르면 JWT는 JSON Web Token의 약자로 JSON 객체를 통해 안전하게 정보를 전송하기 위한 방식이라고 되어있다. 
JWT는 주로 정보를 암호화하여 주고받을 때, 혹은 로그인 등의 인증이 필요할 때 사용한다.</p>
<h3 id="2-jwt의-구조">2. JWT의 구조</h3>
<p>JWT는 세 부분으로 이루어져 있으며 각 부분은 온점(.)에 의해 구분된다.
세 부분은 각각 다음과 같다.</p>
<ul>
<li>Header</li>
<li>Payload</li>
<li>Signature</li>
</ul>
<p>따라서 JWT는 다음과 같은 구조를 가진다.</p>
<blockquote>
<p>xxxxx(header).yyyyy(payload).zzzzz(signature)</p>
</blockquote>
<h3 id="3-1-header">3-1. Header</h3>
<p>Header는 주로 두 가지의 부분으로 이루어져 있다.
토큰의 타입과 암호화 알고리즘의 종류이다.</p>
<pre><code>{
  &quot;alg&quot;: &quot;HS256&quot;,
  &quot;typ&quot;: &quot;JWT&quot;
}</code></pre><h3 id="3-2-payload">3-2. Payload</h3>
<p>Payload는 claim들을 저장하고 있다. claim은 개체에 대한 정보(주로 사용자에 대한 정보)와 추가적인 정보를 가지고 있다. claim에는 세 가지 종류, registered, public, private이 있다.</p>
<ul>
<li><p>registered
미리 정의된 claim들로 iss(발행자), exp(만료시간), sub(토큰 이름), aud(토큰 발행 대상) 등등이 있다.</p>
</li>
<li><p>public
사용자가 원하는 대로 사용할 수 있는 claim이다. 하지만 공개 claim이기 때문에, 충돌을 방지하기 위해 IANA JSON Web Token Registry에 정의된 것들을 사용하거나 충돌이 일어나지 않는 URI만 사용하여야 한다.</p>
</li>
<li><p>private
사용자가 직접 커스텀 할 수 있는 claim으로 registered와 public에 포함되지 않는 정보들을 포함하고 있다.</p>
</li>
</ul>
<pre><code>{
  &quot;sub&quot;: &quot;1234567890&quot;,
  &quot;name&quot;: &quot;John Doe&quot;,
  &quot;admin&quot;: true
}</code></pre><h3 id="3-3-signature">3-3. Signature</h3>
<p>암호화된 Header와 payload, 암호화 알고리즘이 담겨있다.
Signature는 메세지가 전송 중에 변경되지 않았음을 판별하기 위해 사용한다.</p>
<pre><code>HMACSHA256(
  base64UrlEncode(header) + &quot;.&quot; +
  base64UrlEncode(payload),
  secret)</code></pre><h3 id="최종적인-형태">최종적인 형태</h3>
<p><img src="https://velog.velcdn.com/images/binary_j/post/ca851461-174f-4115-83eb-9b4903cfab69/image.png" alt=""></p>
<p>공식 사이트(<a href="https://jwt.io/#debugger-io)%EC%97%90%EC%84%9C">https://jwt.io/#debugger-io)에서</a> 직접 토큰을 만들거나 해독해 볼 수 있다.
참고로 위의 이미지의 Payload에 보이는 iat는 발행시간을 뜻한다.
이유는 모르겠지만 공식 사이트에서 기본적으로 iat가 2018년 1월로 되어있기 때문에..--;;
공식 사이트에서 토큰을 발행할 때에는 iat를 조정하는 것을 추천한다.</p>
<h3 id="reference">Reference</h3>
<p><a href="https://jwt.io">https://jwt.io</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSS 가상요소 before과 after]]></title>
            <link>https://velog.io/@binary_j/CSS-%EA%B0%80%EC%83%81%EC%9A%94%EC%86%8C-before%EA%B3%BC-after</link>
            <guid>https://velog.io/@binary_j/CSS-%EA%B0%80%EC%83%81%EC%9A%94%EC%86%8C-before%EA%B3%BC-after</guid>
            <pubDate>Sun, 05 Jun 2022 08:23:59 GMT</pubDate>
            <description><![CDATA[<h3 id="가상요소">가상요소</h3>
<p>요소의 특정한 부분을 꾸며줄 때 사용함
실제로 어떤 요소의 앞이나 뒤에 다른 요소가 있는 것은 아니지만 있는 것처럼 CSS 속성을 사용할 수 있음</p>
<h3 id="before-after">before after</h3>
<p>before나 after도 가상 요소 종류인데
before같은 경우 해당 요소의 앞에, after 같은 경우 해당 요소의 뒤에 속성을 부여할 수 있음
이 외에도 selection, marker, first-letter, first-line등 다양한 가상 요소가 존재함</p>
<h3 id="사용하는-이유">사용하는 이유</h3>
<p>가상 요소를 사용하면 함수를 추가하거나 불필요한 요소를 추가할 필요 없이 요소를 꾸며줄 수 있기 때문에 편리함</p>
<h3 id="예시">예시</h3>
<pre><code>&lt;ul&gt;
    &lt;li&gt;test1&lt;/li&gt;
    &lt;li&gt;test2&lt;/li&gt;
    &lt;li&gt;test3&lt;/li&gt;
    &lt;li&gt;test4&lt;/li&gt;
&lt;/ul&gt;

&lt;style&gt;
  li::before {
      content: &quot;before로 이렇게 꾸며줄 수 있다! : &quot;;
  }
&lt;/style&gt;</code></pre><p>결과물:
<img src="https://velog.velcdn.com/images/binary_j/post/2a2f4b10-d3f7-442a-9e8e-eab737c5886d/image.png" alt=""></p>
<h3 id="reference">reference</h3>
<p><a href="https://blogpack.tistory.com/1025">https://blogpack.tistory.com/1025</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클린코드(1/5)]]></title>
            <link>https://velog.io/@binary_j/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C15</link>
            <guid>https://velog.io/@binary_j/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C15</guid>
            <pubDate>Sun, 22 May 2022 10:30:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>우리 모두는 대충 짠 프로그램이 돌아간다는 사실에 안도감을 느끼며 그래도 안 돌아가는 프로그램보다 돌아가는 쓰레기가 좋다고 스스로를 위로한 경험이 있다.</p>
</blockquote>
<p>전부터 읽어야겠다고 한 클린코드를 읽기 시작했다.
취준 할 때는 책 내용을 일일히 정리했었는데 이제 그렇게 하는 것은 조금 무리가 있을 것 같아서 우선 학습을 한 후 내가 기록하고 싶은 내용 위주로만 기록을 남기기로 했다.</p>
<hr>
<h3 id="1장--깨끗한-코드">1장 : 깨끗한 코드</h3>
<p>나쁜 코드는 생산성을 떨어트린다.
좋은 코드를 짜는 것은 프로그래머의 역할이다.
좋은 코드란 무엇인가?</p>
<ul>
<li>한 가지에 집중</li>
<li>크기가 작아야 함</li>
<li>테스트를 통해 검증됨</li>
<li>단순명로하게 작성</li>
<li>다른 사람이 읽었을 때도 바로 이해할 수 있어야함</li>
<li>명쾌해야함</li>
<li>효율적이어야 함(속도, CPU 자원 등등)</li>
<li>중복이 없어야 함</li>
<li>보이스카우트 규칙: 캠프장은 처음 왔을 때보다 더 깨끗하게 해놓고 떠나라 -&gt; 지속적인 개선을 통해 점점 더 깨끗한 코드로</li>
</ul>
<hr>
<h3 id="2장--의미-있는-이름">2장 : 의미 있는 이름</h3>
<ul>
<li>의도를 분명하게 밝히는 이름</li>
<li>존재 이유</li>
<li>수행 기능</li>
<li>사용 방법</li>
<li>그릇된 정보를 피해라</li>
<li>널리 쓰이는 이름 사용x</li>
<li>실제로 그 자료형이 아니라면 이름에 자료형 넣지 말 것 (ex. List가 아니라면 변수 이름에 list를 붙이지 말 것)</li>
<li>흡사한 이름 여러개x</li>
<li>의미 있게 구분하라</li>
<li>연속된 숫자를 붙이거나 불용어를 붙이는 식으로 이름붙이지 말 것</li>
<li>읽는 사람이 명확하게 기능을 구분할 수 있는 이름을 붙일 것(ex. ProductInfo, ProductData 이런식의 이름을 붙여선 안됨)</li>
<li>발음하기 쉬운 이름을 사용할 것</li>
<li>앞글자만 따겠다고 genymdhms 이딴 발음할 수 없는 이름 붙이지 말 것</li>
<li>검색하기 쉬운 이름을 사용할 것</li>
<li>검색하기 어려운 짧은 이름(ex. int e1)은 로컬 변수에서만 사용</li>
<li>범위가 커질수록 이름도 길게 붙이는 것이 검색할 때 빠르고 쉬움</li>
<li>인코딩을 피하라</li>
<li>자신의 기억력을 자랑하지 마라</li>
<li>읽는 사람이 계속해서 머릿속으로 변수 이름을 생각하고 사용해야 한다면 나쁜 코드임 (ex. a, b가 이미 있으니 어떤 변수 이름을 c로 붙임 이런식으로 이름붙이지 말 것)</li>
<li>변수 이름은 명료해야 한다.</li>
<li>클래스 이름</li>
<li>클래스/객체 이름은 명사나 명사구로 사용</li>
<li>메서드 이름</li>
<li>메서드 이름은 동사나 동사구로 사용</li>
<li>접근자, 변경자, 조건자는 표준에 따라 get, set, is 접두사로 붙임</li>
<li>기발한 이름을 사용하지 말 것</li>
<li>변수 이름으로 특정 문화권에서만 이해할 수 있는 농담같은거 붙이지 말 것</li>
<li>한 개념에 한 단어</li>
<li>추상적인 개념 하나에 단어 하나를 선택하고 이를 유지할 것 (ex. 날짜를 가져오는 메서드를 getDate()라고 하기로 했으면 다른 모든 클래스에서도 날짜를 가져오는 메서드를 선언할 때 getDate()라고 이름 붙일 것)</li>
<li>해법 영역에서 가져온 이름 사용</li>
<li>컴퓨터 공학에서 사용되는 용어로 이름 붙이기</li>
<li>문제 영역에서 가져온 이름 사용</li>
<li>적절한 프로그래머 용어가 없다면 문제 영역에서 이름 가져오기</li>
<li>의미 있는 맥락 추가</li>
<li>불필요한 맥락은 없애라</li>
</ul>
<hr>
<h3 id="3장--함수">3장 : 함수</h3>
<ul>
<li>작게 만들어라</li>
<li>함수는 작을수록 좋다</li>
<li>함수는 한가지만 해야한다</li>
<li>추상화 수준이 하나인 단계만 수행</li>
<li>함수 내에서 의미 있는 또 다른 함수를 추출해낼 수 있다면 그 함수는 여러가지를 하는 것이므로 축소 필요</li>
<li>함수 당 추상화 수준은 하나로</li>
<li>내려가기 규칙: 함수들은 위에서 아래로 내려가듯이 읽혀야 함, 추상화 수준이 한 번에 한 단계씩 낮아지도록 설계하기</li>
<li>Switch 문</li>
<li>추상 팩토리로 숨기기.. 되도록 사용x</li>
<li>서술적인 이름 사용</li>
<li>길고 서술적인 이름이 짧고 어려운 이름보다 좋다</li>
<li>이름은 일관성 있게 붙이기</li>
<li>함수 인수</li>
<li>적을수록 좋다</li>
<li>인수가 없는 것이 최선이며 차선은 인수가 한 개인 것, 인수 4개 이상은 사용하지 말 것</li>
<li>많이 쓰는 단항 형식: 인수에 질문을 넘기는 경우, 인수를 변환해 결과를 반환하는 경우, 이벤트를 사용하는 경우</li>
<li>플래그 인수: 사용하지 말 것</li>
<li>이항 이상의 함수: 가능하면 단항 함수로 바꾸도록 노력해야 함, (x,y)좌표처럼 자연스럽고 납득 가능한 순서가 있는 경우에만 사용</li>
<li>인수 객체: 인수 여러개를 객체로 묶기(ex. double x, double y -&gt; Point center 이런식으로)</li>
<li>인수 목록: 가변 인수도 마찬가지</li>
<li>동사와 키워드 사용</li>
<li>부수 효과를 일으키지 마라</li>
<li>함수 내에서 함수 이름에 명시되지 않은 다른 동작을 하지 말 것</li>
<li>차라리 함수 내에서 일어나는 동작이 모두 표현되도록 이름을 아주 길게 붙이거나 함수를 조각조각 분해해서 여러개로 만드는 게 낫다</li>
<li>명령과 조회 분리</li>
<li>오류 코드보다 예외 사용</li>
<li>try/catch블록 -&gt; 복잡하고 추한 코드, 별도 함수로 뽑아낼 것</li>
<li>함수는 한 가지 일만 해야함 -&gt; 오류도 하나의 작업임</li>
<li>반복 없애기</li>
<li>구조적 프로그래밍: goto 절대 절대 절대 사용하지 말 것</li>
<li>다듬고, 함수를 만들고, 이름을 변경하고, 중복을 제거하고, 메서드를 줄이고 순서를 바꾸고, 클래스를 쪼개며 계속해서 변경해 나가기(단위 테스트는 항상 통과해야 한다)</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>