-
[Aws + Spring] Spring Boot + Aws Glue + S3๋ฅผ ํ์ฉํ ๋ฐฉ๋ฌธ์ ํต๊ณ ๊ตฌ์ถ (1)Spring 2024. 1. 2. 22:48๋ฐ์ํ
๐ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ ๋ ๋ฐฉ๋ฌธ์ ํต๊ณ ๋ฐ ์ค์๊ฐ ๋ฐ์ดํฐ ๋ถ์์ํ์ ์์์ ๋๋ค.
์ด๋ฒ ํฌ์คํ ์์๋ Spring Boot, AWS Glue, ๊ทธ๋ฆฌ๊ณ S3๋ฅผ ๊ฒฐํฉํ์ฌ ๋ฐฉ๋ฌธ์ ํต๊ณ ์์คํ ์
๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด๊ฒ ์ต๋๋ค.
์ด ํฌ์คํ ์์๋ ์๋ ๋ ๊ฐ์ง ํต๊ณ๋ฅผ ๊ตฌ์ถํ ์์ ์ ๋๋ค.
1. ์ผ์ผ ๋ฐฉ๋ฌธ์ ๋๋ฐ์ด์ค ํต๊ณ (PC, ๋ชจ๋ฐ์ผ, ํ ๋ธ๋ฆฟ ์)
2. ์ผ์ผ ์ฌ์ดํธ ํต๊ณ (๋ฐฉ๋ฌธ์ ์, ํ์ด์ง ๋ทฐ, ํ์๊ฐ์ ์)
๐ฏ ์ด ํฌ์คํธ๋ฅผ ํตํด ์ป์ ์ ์๋ ๊ฒ:
- AWS Glue๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ETL ํ๋ก์ธ์ค ์๋ํ
- AWS S3๋ฅผ ํ์ฉํ ํ์ฅ ๊ฐ๋ฅํ ํต๊ณ ๋ฐ์ดํฐ ์ ์ฅ์ ๊ตฌ์ถ
- ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐฉ๋ฌธ์ ํต๊ณ ํ๋ก์ธ์ค ๊ตฌ์ถ
โ๏ธ ๋ชฉ์ฐจ
1. AWS S3 ๋ก๊ทธ ํด๋ ์์ฑ
2. Spring Boot ์ค์
3. Spring Boot์์์ ๋ฐฉ๋ฌธ์ ํต๊ณ ์์ง API ๊ฐ๋ฐ
4. AWS Glue ์์ ๋ฐ ์คํฌ๋ฆฝํธ ์์ฑ์ค์ตํ๊ฒฝ
- Intellij Ultimate
- JAVA 17
- Spring Boot 2.7.3
- Spring Security
- Maven
- MySQL
1. AWS S3 ๋ก๊ทธ ํด๋ ์์ฑ
๋จผ์ ๋ก๊ทธ ํ์ผ์ ์์ AWS S3์ ๋๋ ํ ๋ฆฌ๋ฅผ ์ถ๊ฐ ํด์ค์ผ ํฉ๋๋ค.
S3 ๋ฒํท์ ์์ฑ ๋์ด ์๋ค๋ ์ ์ ํ์ ์งํ ํ๊ฒ ์ต๋๋ค.
S3 ๋ฒํท ์์ฑ์ ์๋ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/create-bucket-overview.html
๋ฃจํธ ๋๋ ํ ๋ฆฌ ํ์์ /log/site-visit directory๋ฅผ ์ถ๊ฐํฉ๋๋ค. ์ด๋ ๊ฒ ๋ก๊ทธ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ S3 Bucket์ Directory๋ ์ค๋น ๋์ต๋๋ค.์ผ์ผ ์ฌ์ดํธ ํต๊ณ๋ฅผ ์ํ site-signup, site-page-view directory๋ ์ถ๊ฐ ํด๋๊ฒ ์ต๋๋ค.
2. Spring Boot ์ค์
๋จผ์ , AWS์ ๋ก๊ทธ ํ์ผ์ ์๊ธฐ ์ํด์ Aws Client ์ค์ ์ ํด์ค์ผ ํฉ๋๋ค.
Maven Dependency์ s3 sdk๋ฅผ ์ถ๊ฐ ํ๊ฒ ์ต๋๋ค.<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-s3</artifactId> <version>1.12.470</version> </dependency>
Aws Client ์ค์ ์ ํ๋ AwsConfig Class๋ฅผ ์์ฑํ๊ณ S3 Bucket ์ ๋ณด์ Credentials ์ ๋ณด๋ฅผ ์ด๊ธฐํ ํฉ๋๋ค.
๋จผ์ , ๊ด๋ จ ์ ๋ณด๋ฅผ application.yml์ ์ด๊ธฐํ ํด์ค์ผ ํฉ๋๋ค.
๐ application.ymlcloud: aws: credentials: access-key: {Your Access Key} secret-access-key: {Your Secret Key} s3: bucket: {Your Bucket Name} region: static: ap-northeast-2
ํต๊ณ์ฉ ๋ก๊ทธ ๋ฐ์ดํฐ ํ์ผ์ ์์ฑํ๊ณ S3์ ์ ๋ก๋๋ฅผ ํ๋ Method๋ฅผ ์ถ๊ฐ ํ๊ธฐ ์ํด AwsComponent๋ฅผ ์์ฑํฉ๋๋ค.
AmazonS3 ํด๋์ค์ putObject method๋ก ๋ก๊ทธ ํ์ผ์ ์์ฑํ ์ ์์ต๋๋ค.
๐ AwsComponent.class@Slf4j @Builder public class AwsComponent { private Environment environment; private String bucketName; private AmazonS3 amazonS3; private String accessKey; private String secretKey; public void saveToFile(String path, String content) { log.info("save file path : {}", path); try { amazonS3.putObject(bucketName, path, content); } catch (AmazonS3Exception e) { log.error("save file error : path[{}], message[{}]", path, e.getMessage()); throw e; } } }
์์์ ์์ฑํ AwsComponent์ AwsClient ๋ฐ S3 ์ ๋ณด๋ฅผ ์ด๊ธฐํ ํ๊ธฐ ์ํ AwsConfig๋ฅผ ์์ฑํฉ๋๋ค.
์ด๋ ๊ฒ Bean์ ์ถ๊ฐ ํด์ฃผ๋ฉด, AwsComponent๋ฅผ DI ํ ๋ ์ค์ ์ ๋ณด๊ฐ ์ด๊ธฐํ ๋์ด ์๊ฒ ๋ฉ๋๋ค.
๐ AwsConfig.class@Configuration @RequiredArgsConstructor public class AwsConfig { private final Environment environment; @Value("${cloud.aws.s3.bucket}") private String bucket; @Value("${cloud.aws.credentials.access-key}") private String accessKey; @Value("${cloud.aws.credentials.secret-access-key}") private String secretKey; @Value("${cloud.aws.region.static}") private String region; @Bean public AwsComponent amazonS3Client() { BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); AWSStaticCredentialsProvider staticCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials); return AwsComponent .builder() .bucketName(bucket) .environment(environment) .accessKey(accessKey) .secretKey(secretKey) .amazonS3( AmazonS3ClientBuilder .standard() .withRegion(region) .enablePathStyleAccess() .withCredentials(staticCredentialsProvider) .build()) .build(); } }
์ด๋ ๊ฒ AWS S3 ๊ด๋ จ ์ค์ ๊ณผ ๋ก๊ทธ ํ์ผ์ ์ ๋ก๋ํ๋ Method ์ถ๊ฐ๊น์ง ์๋ฃ ํ์ต๋๋ค.
์ด์ ํต๊ณ ์ ๋ณด๋ฅผ DB์ ์ ์ฅํ๊ธฐ ์ํ ํด๋์ค๋ค์ ์์ฑ ํ๊ฒ ์ต๋๋ค.์ถํ AWS File์ Body์ ์ ๋ ฅํ ํด๋์ค๋ค์ ์์ฑ์๊ฐ ํ๋๋ฅผ ๊ณตํต ํด๋์ค๋ก ์์ฑํฉ๋๋ค.
๐ CommonLog.class
@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @MappedSuperclass public abstract class CommonLog { protected LocalDateTime dateTime = LocalDateTime.now(); protected LocalDate date = dateTime.toLocalDate(); }
์ผ์ผ ๋ฐฉ๋ฌธ์, ๋๋ฐ์ด์ค Entity์ ์์ฑ์๊ฐ์ ๊ณตํต ํด๋์ค๋ก ์์ฑํฉ๋๋ค.
๐ CommonLog.class
@Getter @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public abstract class CommonCreateDateTimeEntity { @Setter @CreatedDate @Column(nullable = false, columnDefinition = "datetime default now()") @Comment("์์ฑ ์ผ์") protected LocalDateTime createdDateTime; }
๋จผ์ ์ฌ์ดํธ ๋ก๊ทธ ์ ํ Enum์ ์์ฑํฉ๋๋ค.
๐ SiteLogType.classpublic enum SiteLogType { VISIT, PAGE_VIEW, }
๋ฐฉ๋ฌธ์ ๋๋ฐ์ด์ค ์ ๋ณด๊ฐ ๋ด๊ฒจ์๋ SiteVisitLog Entity๋ฅผ ์์ฑํฉ๋๋ค. userId๋ ๋ก๊ทธ์ธ ํ ์ฌ์ฉ์์ ๊ณ ์ ํ ์์ด๋ ๊ฐ์ ๋๋ค.
๐ SiteVisitLog.class@Entity @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class SiteVisitLog extends CommonLog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Comment("๋๋ฐ์ด์ค") private String device; @Comment("์ฌ์ฉ์ ์์ด๋") private String userId; private String osFamily; private String osVersion; private String browserFamily; private String browserVersion; @Enumerated(EnumType.STRING) private final SiteLogType type = SiteLogType.VISIT; public SiteVisitLog(String userId) { this.userId = userId; } public SiteVisitLog(String userId, String device, String osFamily, String osVersion, String browserFamily, String browserVersion) { this(userId); this.device = device; this.osFamily = osFamily; this.osVersion = osVersion; this.browserFamily = browserFamily; this.browserVersion = browserVersion; } }
๊ฐ ํ๋์ ์ญํ ๊ณผ ์๋ฏธ์ ๋ํ ๊ฐ๋จํ ์ค๋ช ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- device (๋๋ฐ์ด์ค):
- ์ค๋ช : ์ฌ์ฉ์๊ฐ ์ก์ธ์คํ๋ ๋๋ฐ์ด์ค์ ์ด๋ฆ์ ๋ํ๋ด๋ ๋ฌธ์์ด์ ๋๋ค. ๋จ, Android ๊ฐ์ ๊ฒฝ์ฐ์ device์ 'android'๊ฐ ์๋, 'Samsung-xxx' ์ ๊ฐ์ ์ด๋ฆ์ด ์ ์ฅ ๋ ์ ์์ต๋๋ค. ์ด๋ด ๊ฒฝ์ฐ์ ์๋์ osFamily๋ฅผ ํ์ธํด์ผ ํฉ๋๋ค.
- ์์: "Windows", "Mac OS", "Linux", "Android", "Samsung-xxx", "IPad" ๋ฑ
- osFamily (์ด์์ฒด์ ํจ๋ฐ๋ฆฌ):
- ์ค๋ช : ์ฌ์ฉ์๊ฐ ์ก์ธ์คํ๋ ๋๋ฐ์ด์ค์ ์ด์์ฒด์ ๋ฅผ ๋ํ๋ด๋ ๋ฌธ์์ด์ ๋๋ค.
- ์์: "Windows", "Mac OS", "Linux" ๋ฑ
- osVersion (์ด์์ฒด์ ๋ฒ์ ):
- ์ค๋ช : ์ฌ์ฉ์์ ๋๋ฐ์ด์ค์ ์ค์น๋ ์ด์์ฒด์ ์ ๋ฒ์ ์ ๋ํ๋ด๋ ๋ฌธ์์ด์ ๋๋ค.
- ์์: "10.0.19042" (Windows 10์ ๋ฒ์ ), "11.3.1" (macOS Big Sur์ ๋ฒ์ ) ๋ฑ
- browserFamily (๋ธ๋ผ์ฐ์ ํจ๋ฐ๋ฆฌ):
- ์ค๋ช : ์ฌ์ฉ์๊ฐ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ก์ธ์คํ๋ ๋ฐ ์ฌ์ฉํ๋ ๋ธ๋ผ์ฐ์ ๋ฅผ ๋ํ๋ด๋ ๋ฌธ์์ด์ ๋๋ค.
- ์์: "Chrome", "Firefox", "Safari" ๋ฑ
- browserVersion (๋ธ๋ผ์ฐ์ ๋ฒ์ ):
- ์ค๋ช : ์ฌ์ฉ์๊ฐ ์ฌ์ฉํ๋ ๋ธ๋ผ์ฐ์ ์ ๋ฒ์ ์ ๋ํ๋ด๋ ๋ฌธ์์ด์ ๋๋ค.
- ์์: "94.0.4606.81" (Google Chrome์ ๋ฒ์ ), "92.0.4515.159" (Mozilla Firefox์ ๋ฒ์ ) ๋ฑ
์ผ์ผ ๋ฐฉ๋ฌธ์ ๋๋ฐ์ด์ค ํต๊ณ Entity๋ฅผ ์์ฑํฉ๋๋ค. ๋ฐ์คํฌํ, ๋ชจ๋ฐ์ผ, ํ ๋ธ๋ฆฟ, unknown ๋๋ฐ์ด์ค ์ ๋ณด๋ฅผ ํฌํจํ๊ณ ์์ต๋๋ค.๐ DailyDeviceStats.class
@Entity @Getter @NoArgsConstructor public class DailyDeviceStats extends CommonCreateDateTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private LocalDate date; private Long desktopCount; private Long mobileCount; private Long tabletCount; private Long unknownCount; public DailyDeviceStats(LocalDate date) { this(date, 0L, 0L, 0L, 0L); } public DailyDeviceStats(LocalDate date, Long desktopCount, Long mobileCount, Long tabletCount, Long unknownCount) { this.date = date; this.desktopCount = desktopCount; this.mobileCount = mobileCount; this.tabletCount = tabletCount; this.unknownCount = unknownCount; } }
์ผ์ผ ์ฌ์ดํธ ํต๊ณ Entity๋ฅผ ์์ฑํฉ๋๋ค. ๋ฐฉ๋ฌธ์, ํ์๊ฐ์ , ํ์ด์ง๋ทฐ(PV) ์ ์ ๋ณด๋ฅผ ํฌํจํ๊ณ ์์ต๋๋ค.
๐ DailySiteStats.class
@Entity @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(uniqueConstraints = { @UniqueConstraint(columnNames = {"date"}, name = "daily_site_stats_uk") }) public class DailySiteStats extends CommonCreateDateTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private LocalDate date; private Long visitorCount; private Long signUpCount; private Long pageViewCount; public DailySiteStats(LocalDate date) { this(date, 0L, 0L, 0L); } public DailySiteStats(LocalDate date, Long visitorCount, Long signUpCount, Long pageViewCount) { this.date = date; this.visitorCount = visitorCount; this.signUpCount = signUpCount; this.pageViewCount = pageViewCount; } public void addVisitorCount() { this.visitorCount++; } public void addSignUpCount() { this.signUpCount++; } public void addPageViewCount() { this.pageViewCount++; } }
์ผ๋ณ ์ค๋ณต ๋ฐ์ดํฐ๊ฐ ์์ฑ ๋๋๊ฒ์ ๋ฐฉ์ง ํ๊ธฐ ์ํด์ @UniqueConstraint ์ ์ฝ์กฐ๊ฑด์ ์ค์ ํ์ต๋๋ค.
๋ฐ์ดํฐ ์ ์ฅ์ Lock์ ์ค์ ํด์ ๋์์ฑ ์ ์ด๋ฅผ ์ฒ๋ฆฌํ ์๋ ์์ง๋ง, Lock์ผ๋ก ์ธํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์๋๊ฐ ์ง์ฐ ๋๋ฉด ์๋๋ ๋ฐ์ดํฐ์ด๊ธฐ ๋๋ฌธ์, Unique Key๋ฅผ ์ค์ ํ์์ต๋๋ค.
S3์ body๋ฅผ write ํ๊ธฐ ์ํ ํด๋์ค๋ค์ ์ถ๊ฐ ํ๊ฒ ์ต๋๋ค. ์๋ ํด๋์ค๋ค์ ์ ์ฅ ํ๊ธฐ ์ํ ์ฉ๋๊ฐ ์๋๋ผ์ Entity๊ฐ ์๋๋๋ค.
๐ PageViewLog.class
@Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class PageViewLog extends CommonLog { private String userId; private String uri; private final SiteLogType type = SiteLogType.PAGE_VIEW; public PageViewLog(String userId, String uri) { this.userId = userId; this.uri = uri; } }
๐ SiteSignUpLog.class
@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class SiteSignUpLog extends CommonLog { private String userId; private final SiteLogType type = SiteLogType.SIGN_UP; public SiteSignUpLog(String userId) { this.userId = userId; } }
์์์ ์ถ๊ฐํ ๋ ์ง ํ๋๊ฐ ์ ์ธ ๋์ด ์๋ CommonLog ํด๋์ค๋ฅผ ์์ ๋ฐ์์ต๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก SiteLogType Enum Class๋ก ์ ํ์ ๊ตฌ๋ถํฉ๋๋ค.์ด๋ก์จ, ํ์ํ ๊ธฐ๋ณธ์ ์ธ ํด๋์ค๋ค์ ์์ฑ์ ํด์คฌ์ต๋๋ค. ๋จผ์ ์ฌ์ฉ์์ Device ์ ๋ณด๋ฅผ ์๊ธฐ ์ํด์ Interceptor๋ฅผ ์์ฑํ๊ฒ ์ต๋๋ค.
3. Spring Boot์์์ ๋ฐฉ๋ฌธ์ ํต๊ณ ์์ง API ๊ฐ๋ฐ
UserAccessInterceptor ์์๋ API ํธ์ถ์ด ๋ฐ์ํ ๋๋ง๋ค preHandle method๊ฐ ์คํ๋ฉ๋๋ค. ์ฌ์ฉ์์ ๋ฐฉ๋ฌธ์ผ์ ์ธ์ ์ ์ ์ฅ ํ ๋ค, ์ผ๋ณ๋ก ์ต์ด 1๋ฒ๋ง ์คํํ๊ธฐ ์ํด์ ์ธ์ ์ ๋ฐฉ๋ฌธ์ผ์ด ์๊ฑฐ๋ ์ ์ฅ ๋ ๋ฐฉ๋ฌธ์ผ๊ณผ ๋ค๋ฅธ ๋ ์ง(์ด์ ๋ )์ธ ๊ฒฝ์ฐ,
๋ฐฉ๋ฌธ์์ ๋๋ฐ์ด์ค ์ ๋ณด๋ฅผ DB์ S3์ ์ ์ฅํฉ๋๋ค.๐ CustomInterceptor.class
interceptor๊ฐ ์ฌ๋ฌ๊ฐ์ธ ๊ฒฝ์ฐ๋ฅผ ์ํด HandlerInterceptor๋ฅผ ์์ ๋ฐ์ interface๋ฅผ ์์ฑํฉ๋๋ค.
์ดํ InterceptorRegistry์ pathPattern๋ฅผ ์ถ๊ฐํ๊ธฐ ์ํด pathPattern ๋ฉ์๋๋ ์ ์ธํฉ๋๋ค.public interface CustomInterceptor extends HandlerInterceptor { String pathPattern(); }
๐ UserAccessInterceptor.class
์ด ํด๋์ค๋ CustomInterceptor๋ฅผ ๊ตฌํํ๊ณ ์์ผ๋ฉฐ, Spring์ HandlerInterceptor๋ฅผ ํ์ฅํ์ฌ API ์๋ํฌ์ธํธ์ ์ ๊ทผํ ์ฌ์ฉ์์ ๋๋ฐ์ด์ค ์ ๋ณด๋ฅผ ๋ก๊น ํ๋ ์ญํ ์ ํฉ๋๋ค.
@Component public class UserAccessInterceptor implements CustomInterceptor { @Autowired private LoggingWebService loggingWebService; private final Parser uaParser = new Parser(); private static final GrantedAuthority USER_AUTHORITY = ROLE_AUTHORITIES_MAP.get(UserRole.NORMAL); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(false); if (session == null) return true; Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if (principal instanceof SessionUser sessionUser) { if (!sessionUser.getAuthorities().contains(USER_AUTHORITY)) return true; // ์ฌ์ฉ์์ ๋ง์ง๋ง ๋ก๊ทธ์ธ ์๊ฐ์ ์ ์ฅํด๋๊ณ , ํ์ฌ ์๊ฐ๊ณผ ๋น๊ตํ์ฌ ๋ค๋ฅด๋ฉด ๋ก๊ทธ๋ฅผ ๋จ๊ธด๋ค. Object lastLoginDate = session.getAttribute("lastLoginDate"); LocalDate today = LocalDate.now(); if (lastLoginDate == null || !((LocalDate)lastLoginDate).isEqual(today)) { session.setAttribute("lastLoginDate", today); String userAgent = defaultString(request.getHeader("User-Agent"), ""); logDeviceInfo(sessionUser.getId(), userAgent); } } return true; } @Override public String pathPattern() { return "/api/**"; } private void logDeviceInfo(String userId, String userAgent) { Client client = uaParser.parse(userAgent); String deviceFamily = client.device.family; String osFamily = client.os.family; String osVersion = client.os.major + "." + client.os.minor; String browserFamily = client.userAgent.family; String browserVersion = client.userAgent.major + "." + client.userAgent.minor; loggingWebService.addSiteVisitLog(userId, deviceFamily, osFamily, osVersion, browserFamily, browserVersion); } }
UserAccessInterceptor์ ๋ํ ์ค๋ช ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- UserAgent ํ์ฑ:
- User-Agent ์ ๋ณด๋ฅผ ํ์ฑํ๊ธฐ ์ํ uaParser ์ธ์คํด์ค๋ฅผ ์์ฑํฉ๋๋ค.
- ์ฌ์ฉ์ ๊ถํ ๋ฐ ๋ก๊ทธ์ธ ์๊ฐ ๋ก๊น
:
- preHandle ๋ฉ์๋์์๋ ํ์ฌ ์ฌ์ฉ์์ ์ธ์ ์ ๋ณด๋ฅผ ํ์ธํ๊ณ , ํด๋น ์ฌ์ฉ์๊ฐ ์ผ๋ฐ ์ฌ์ฉ์ ๊ถํ์ ๊ฐ์ง๊ณ ์๋ ๊ฒฝ์ฐ(UserRole.NORMAL), ๋ง์ง๋ง ๋ก๊ทธ์ธ ์๊ฐ์ ํ์ธํ์ฌ ๋ค๋ฅธ ๋ ์ง์ธ ๊ฒฝ์ฐ์๋ง ๋ก๊ทธ์ธ ๊ธฐ๊ธฐ ์ ๋ณด๋ฅผ ๋ก๊น ํฉ๋๋ค.
- ๋ก๊ทธ์ธ ์๊ฐ์ ์ธ์ ์ ์์ฑ์ผ๋ก ์ ์ฅ๋์ด ๋ค์ ๋ก๊ทธ์ธ ์ ๋น๊ต์ ์ฌ์ฉ๋ฉ๋๋ค.
- ๋ก๊ทธ์ธ ๊ธฐ๊ธฐ ์ ๋ณด ๋ก๊น
:
- logDeviceInfo ๋ฉ์๋์์๋ User-Agent ์ ๋ณด๋ฅผ ํ์ฑํ๊ณ , ํด๋น ์ ๋ณด๋ฅผ ์ด์ฉํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ๊ธฐ๊ธฐ ์ ๋ณด๋ฅผ ์ถ์ถํฉ๋๋ค.
- ์ถ์ถ๋ ์ ๋ณด๋ loggingWebService๋ฅผ ํตํด ๋ก๊ทธ๋ก ๊ธฐ๋ก๋ฉ๋๋ค.
- ์ ์ฉ ๊ฒฝ๋ก:
- pathPattern ๋ฉ์๋์์๋ ์ด ์ธํฐ์ ํฐ๊ฐ ์ด๋ค ๊ฒฝ๋ก์ ์ ์ฉ๋๋์ง๋ฅผ ๋ํ๋ด๋ ํจํด์ ์ง์ ํฉ๋๋ค. ํ์ฌ๋ /api/** ํจํด์ผ๋ก ์ค์ ๋์ด API ์๋ํฌ์ธํธ์๋ง ์ ์ฉ๋ฉ๋๋ค.
ํด๋น interceptor๋ฅผ ๊ตฌํ ํ๋ค๊ณ ์คํ ๋๋๊ฒ์ด ์๋๋ผ, InterceptorRegistry์ interceptor๋ฅผ ์ถ๊ฐํด์ค์ผ ํฉ๋๋ค.
๐ WebConfig.class
@Configuration @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { private final Environment environment; private final List<CustomInterceptor> interceptors; @Override public void addInterceptors(InterceptorRegistry registry) { for (CustomInterceptor interceptor : interceptors) { registry.addInterceptor(interceptor) .addPathPatterns(interceptor.pathPattern()); } } }
CustomInterceptor Interface ๋ชฉ๋ก์ DI ํ ๋ค, addInterceptors override method์ pathPattern๊ณผ ํจ๊ป interceptor๋ฅผ ์ถ๊ฐํฉ๋๋ค. ์ด์ "/api/**" ๊ฒฝ๋ก์ API๊ฐ ํธ์ถ ๋ ๋๋ง๋ค UserAccessInterceptor์ prehandle method๊ฐ ์คํ๋ฉ๋๋ค.
๋ด์ฉ์ด ๊ธธ์ด์ง๋ ๊ด๊ณ๋ก ๋ค์ ํฌ์คํ ์์ ์ด์ด์ ์์ฑํ๊ฒ ์ต๋๋ค.
๋ฐ์ํ'Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ