๐ ๋จผ์ , ๋ค์์ ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ์ฐจ์ ์ผ๋ก ๋ฐ์ ์์ผ ๋๊ฐ๋ณด๊ฒ ์ต๋๋ค.
User.Java(Domain)
@Getter
@NoArgsConstructor(access= AccessLevel.PROTECTED)
public class User {
private String id;
private String name;
private String password;
private Level level;
private int login;
private int recommend;
private String email;
private LocalDateTime createdAt;
private LocalDateTime lastUpgraded;
public void updateLevel(Level level){
this.level=level;
}
public void updateLogin(int login){
this.login=login;
}
public void updateRecommend(int recommend){
this.recommend=recommend;
}
public void upgradeLevel(){
Level next=this.level.getNext();
if(next==null){
throw new IllegalStateException(this.level+"์ ํ์ฌ ์
๊ทธ๋ ์ด๋๊ฐ ๋ถ๊ฐ๋ฅํฉ๋๋ค.");
}
updateLevel(next);
this.lastUpgraded=LocalDateTime.now();
}
@Builder(builderMethodName = "createUser")
public User(String id, String name, String password,Level level,int login,int recommend, String email
,LocalDateTime createdAt,LocalDateTime lastUpgraded){
this.id=id;
this.name=name;
this.password=password;
this.level=level;
this.login=login;
this.recommend=recommend;
this.email=email;
this.createdAt=createdAt;
this.lastUpgraded=lastUpgraded;
}
}
UserDao.java
public interface UserDao {
void add(User user);
Optional<User> get(String id);
List<User> getAll();
void deleteAll();
int getCount();
void update(User user);
}
UserDaoImpl.java
@RequiredArgsConstructor
public class UserDaoImpl implements UserDao {
private final JdbcOperations jdbcOperations;
private RowMapper<User> userMapper=new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.createUser()
.id(rs.getString("id"))
.password(rs.getString("password"))
.name(rs.getString("name"))
.level(Level.valueOf(rs.getInt("level")))
.login(rs.getInt("login"))
.recommend(rs.getInt("recommend"))
.email(rs.getString("email"))
.createdAt(rs.getTimestamp("createdAt").toLocalDateTime())
.lastUpgraded(rs.getTimestamp("lastUpgraded").toLocalDateTime())
.build();
}
};
@Override
public void add(User user) {
jdbcOperations.update("insert into users(id,name,password,level,login,recommend,email,createdAt,lastUpgraded) " +
"values(?,?,?,?,?,?,?,?,?)",
user.getId(),user.getName(),user.getPassword()
,user.getLevel().getValue(),user.getLogin(),user.getRecommend(), user.getEmail()
, Timestamp.valueOf(user.getCreatedAt()),Timestamp.valueOf(user.getLastUpgraded()));
}
@Override
public Optional<User> get(String id) {
return Optional.ofNullable(
jdbcOperations.queryForObject("select * from users u where u.id=?", userMapper,new Object[]{id}));
}
@Override
public List<User> getAll() {
return jdbcOperations.query("select * from users u order by id", userMapper);
}
@Override
public void deleteAll() {
jdbcOperations.update("delete from users");
}
@Override
public int getCount() {
return jdbcOperations.queryForObject("select count(*) from users",Integer.class);
}
@Override
public void update(User user) {
jdbcOperations.update("update users set name=?,password=?,level=?,login=?,recommend=?,email=?, createdAt=?," +
"lastUpgraded=? where id=?",
user.getName(), user.getPassword(),user.getLevel().getValue()
,user.getLogin(),user.getRecommend(),user.getEmail(),user.getCreatedAt(),user.getLastUpgraded(),user.getId());
}
}
UserService.java
public interface UserService {
void add(User user);
void upgradeLevels();
User get(String id);
List<User> getAll();
int getCount();
void deleteAll();
void update(User user);
}
UserServiceImpl.java
package study.aop.service;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import study.aop.domain.Level;
import study.aop.dao.UserDao;
import study.aop.domain.User;
import java.util.List;
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserDao userDao;
private final PlatformTransactionManager transactionManager;
private final MailSender mailSender;
public static final int LOG_COUNT_FOR_SILVER=50;
public static final int REC_COUNT_FOR_GOLD=30;
public void upgradeLevels(){
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try{
upgradeLevelsInternal();
transactionManager.commit(status);
}catch(Exception e){
transactionManager.rollback(status);
throw e;
}
}
public void add(User user){
if(user.getLevel()==null)
user.updateLevel(Level.BASIC);
userDao.add(user);
}
protected void upgradeLevel(User user){
user.upgradeLevel();
userDao.update(user);
sendUpgradeMail(user);
}
@Override
public User get(String id) {
return userDao.get(id).orElseThrow(()->{
throw new RuntimeException();
});
}
@Override
public List<User> getAll() {
return userDao.getAll();
}
@Override
public int getCount() {
return userDao.getCount();
}
@Override
public void deleteAll() {
userDao.deleteAll();
}
@Override
public void update(User user) {
userDao.update(user);
}
private void sendUpgradeMail(User user){
SimpleMailMessage mailMessage=new SimpleMailMessage();
mailMessage.setTo(user.getEmail());
mailMessage.setFrom("admin");
mailMessage.setSubject("Upgrade ์๋ด");
mailMessage.setText("์ฌ์ฉ์๋์ ๋ฑ๊ธ์ด "+user.getLevel()+"๋ก ์
๊ทธ๋ ์ด๋ ๋ ์์ต๋๋ค.");
mailSender.send(mailMessage);
}
private boolean checkUpgrade(User user){
Level currentLevel=user.getLevel();
if(currentLevel==Level.BASIC)
return (user.getLogin()>=LOG_COUNT_FOR_SILVER);
else if(currentLevel==Level.SILVER)
return (user.getRecommend()>=REC_COUNT_FOR_GOLD);
else if(currentLevel==Level.GOLD)
return false;
throw new IllegalArgumentException("Unknown Level : "+currentLevel);
}
}
AppConfig.java
@Configuration
@RequiredArgsConstructor
public class AppConfig {
private final Environment env;
@Bean
public DataSource dataSource(){
SimpleDriverDataSource dataSource=new SimpleDriverDataSource();
dataSource.setDriverClass(org.h2.Driver.class);
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(dataSource());
}
@Bean
public JdbcOperations jdbcOperations(){
return new JdbcTemplate(dataSource());
}
@Bean
public UserDao userDao(){
return new UserDaoImpl(jdbcOperations());
}
@Bean
public MailSender mailSender(){
return new DummyMailSender();
}
@Bean
public UserService userService(){
return new UserServiceImpl(userDao(),mailSender());
}
}
๋จผ์ , UserServiceImpl์ ์ฝ๋์ UpgradeLevels ๋ฉ์๋๋ฅผ ์ดํด๋ด ์๋ค.
public void upgradeLevels(){
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try{
upgradeLevelsInternal();
transactionManager.commit(status);
}catch(Exception e){
transactionManager.rollback(status);
throw e;
}
}
์ง๊ธ ์ด ๋ฉ์๋๋ Transaction ๊ฒฝ๊ณ์ค์ ์ฝ๋์ ๋น์ฆ๋์ค ๋ก์ง ์ฝ๋๊ฐ ์๋ก ๊ณต์กดํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๊ทธ๋ ๋ค๊ณ ํ๋๋ผ๋ ์ฝ๋๋ฅผ ์ ์ดํด๋ณด์๋ฉด ์๋ก๊ฐ์ ์ฐ๊ด๊ด๊ณ๊ฐ ๊ฑฐ์ ์์ผ๋ฉฐ, ๋๋ ท์ด ๊ฐ์์ ๊ธฐ๋ฅ๋ค์ ํ๊ณ ์๋ค๋ ๊ฒ์ ์์๋ณผ ์ ์์ต๋๋ค.
1. Transaction ๊ฒฝ๊ณ์ค์ ์ฝ๋๋ ์์๊ณผ ์ข
๋ฃ๋ง์ ๋ด๋น.
2. ์คํ๋ง์ด ์ ๊ณตํ๋ Transaction (์๋น์ค ์ถ์ํ๊ฐ ๋์ด์์!)๊ณผ ๊ณ์ธต ๋ถ๋ฆฌ๊ฐ ์๋์ด์ง Repository
์ Service
๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ๋น์ฆ๋์ค ๋ก์ง ์ฝ๋๋
์ง์ DB๋ฅผ ๋ค๋ฃจ์ง ์์ ๋ฟ๋๋ฌ Transaction ๊ฒฝ๊ณ๊ธฐ๋ฅ์ ํ๋ ์ฝ๋์ ์ ๋ณด๋ฅผ ์ฃผ๊ณ ๋ฐ์ง ์์ต๋๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก, ์ด ๋๊ฐ์ง ๊ธฐ๋ฅ์ ์ฝ๋๋ ์๋ก ์ฑ๊ฒฉ์ด ๋ค๋ฅด๋ค๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค!
๊ทธ๋ ๋ค๋ฉด, ํ์ฌ๋ก์๋ Refactoring์ ํตํ์ฌ ์๋ก๋ฅผ ๋ถ๋ฆฌ์์ผ ๋๋ ๊ฒ์ด ์ต๊ณ ์ ๋ฐฉ๋ฒ์ด ๋ ๊ฒ์ ๋๋ค! ๐
๋น์ฆ๋์ค ๋ก์ง ์ฝ๋์ Transaction ๊ฒฝ๊ณ ์ค์ ์ฝ๋๋ฅผ ๋ถ๋ฆฌํ๊ธฐ ์ํด ๊ฐ์ฅ ๋จผ์ ์๊ฐํด๋ณผ ๋ฐฉ๋ฒ์ ๋ค์ ๋ ๊ฐ์ง ์
๋๋ค.
1. ๋ฉ์๋๋ฅผ ์ด์ฉํ ๋ถ๋ฆฌ
2. DI๋ฅผ ์ด์ฉํ ๋ถ๋ฆฌ
๋ค์ ํ๋ฒ ์๊ฐํด๋ณด๊ฒ ์ต๋๋ค. ์ด๋ป๊ฒ ํ๋ฉด ๋์ฑ ๊น๋ํ ๊น?!
์ด์ฐจํผ ์๋ก ์ฃผ๊ณ ๋ฐ์ ์ ๋ณด๊ฐ ์๋ค๋ฉด, ํธ๋์ญ์
์ฝ๋๋ฅผ ์์จ ์ ์์ผ๋ ๋น์ฆ๋์ค ๋ก์ง์ ํธ๋์ญ์
์ฝ๋๊ฐ ์์ ์๋ณด์ด๋ ๊ฒ์ฒ๋ผ ์ฌ์ฉํ์ฌ๋ณด์!
์ด๋ฐ ๋ฐฉ์์ ํด๊ฒฐ๋ฒ์ ์๊ฐํด๋ณด๋ฉด, ๋ฉ์๋๋ฅผ ์ด์ฉํ ๋ถ๋ฆฌ๋ ์ฒดํ๋ ์ ์์ต๋๋ค. ์๋ํ๋ฉด, ๋ ๊ฐ๋ฐํ ๋ฉ์๋๊ฐ ์๋์ ํด๋์ค์ ์จ์ ํ ๋จ์์๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ฆ, DI๋ฅผ ์ด์ฉํ ๋ถ๋ฆฌ๋ฅผ ์๊ฐํด๋ด์ผ ํฉ๋๋ค!
๐ DI๋ฅผ ์ด์ฉํ ๋ถ๋ฆฌ
์ด๋ค ์๋ฆฌ๋ฅผ ํตํด DI๋ฅผ ์ด์ฉํ ๋ถ๋ฆฌ๊ฐ ๊ฐ๋ฅํ์ง ์ดํด๋ด ์๋ค.
์ผ๋จ์, UserService
ํด๋์ค๋ UserService
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค๋ผ๋ ๊ฒ์ ์๊ฐํด๋ณผ ์ ์์ต๋๋ค.
์ฆ, UserServiceImpl
์ ์ด๋ ํ ํด๋ผ์ด์ธํธ์๋ ๊ฐ๋ ฅํ, ์ง์ ์ ์ธ
๊ฒฐํฉ์ด ๋์ด์์ง ์๊ณ ์ ์ฐํ ํ์ฅ์ด ๊ฐ๋ฅํ ์ํ์
๋๋ค.
ํ์ง๋ง, ์ผ๋ฐ์ ์ธ DI๋ฅผ ์ฐ๋ ์ด์ ๋ฅผ ์๊ฐํด ๋ณธ๋ค๋ฉด, ์ผ๋ฐ์ ์ผ๋ก ๋ฐํ์์ ํ๊ฐ์ง ๊ตฌํ ํด๋์ค๋ฅผ ๊ฐ์๊ฐ๋ฉฐ ์ฌ์ฉํ๊ธฐ ์ํ์ฌ ์ฌ์ฉํ๋ค๊ณ ๋ณด์๋ฉด ๋ฉ๋๋ค.
์ฌ๊ธฐ์, ์ผ๋ฐ์ ์ด๋ผ๋ ๋ง์ ๊ฒฐ์ฝ ์ ํด์ง ์ ์ฝ์ด ์๋๋๊น, ์ผ๋ฐ์ ์ธ DI๊ฐ ์๋ ํ ๋ฒ์ ๋ ๊ฐ์ง ๊ตฌํํด๋์ค๋ฅผ ๋์์ ์ด์ฉํด๋ณธ๋ค๋ฉด ์ด๋จ๊น๋ผ๋ ์๊ฐ์ ํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ์๊ฐํด๋ณผ ์ ์์ต๋๋ค.
UserService
๋ฅผ ๊ตฌํํ๋ ๋๋ค๋ฅธ ๊ตฌํ ํด๋์คUserServiceTx
๋ฅผ ๋ง๋ ๋ค.UserServiceTx
๋ ํธ๋์ญ์
๊ฒฝ๊ณ ์ค์ ์ฑ
์๋ง์ ๋งก๊ณ , ๋น์ฆ๋์ค ๋ก์ง์ ๋ ๋ค๋ฅธ ๊ตฌํ ํด๋์ค์ธ UserServiceImpl
์๊ฒ ๋งก๊ธด๋ค.๊ฒฐ๊ณผ์ ์ผ๋ก, ์ด๋ฐ ๊ตฌ์กฐ๋ฅผ ํตํด ์์์ ์ํ ํธ์ถ ์์
์ด์ ์ดํ์ ํธ๋์ญ์
๊ฒฝ๊ณ๋ฅผ ์ค์ ํด์ฃผ๊ฒ ๋๋ค๋ฉด,
ํด๋ผ์ด์ธํธ ์
์ฅ์์๋ ํธ๋์ญ์
๊ฒฝ๊ณ์ค์ ์ด ๋ถ์ฌ๋ ๋น์ฆ๋์ค ๋ก์ง์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋๊ฐ์ด ๋ฉ๋๋ค.
์ด๋ฌํ ๋ฐฉ๋ฒ์ ์ด์ฉํด ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
UserServiceTx.java
@RequiredArgsConstructor
public class UserServiceTx implements UserService{
private final UserService userService;
private final PlatformTransactionManager transactionManager;
@Override
public void add(User user) {
userService.add(user);
}
@Override
public void upgradeLevels() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
userService.upgradeLevels();
transactionManager.commit(status);
}catch(RuntimeException e){
transactionManager.rollback(status);
throw e;
}
}
}
UserServiceImpl.java - UpgradeLevels๋ฉ์๋
public void upgradeLevels(){
List<User> users=userDao.getAll();
for(User user:users){
if(checkUpgrade(user)){
upgradeLevel(user);
}
}
}
AppConfig.java - DI
@Bean
public UserService userServiceTx(){
return new UserServiceTx(userService(),transactionManager());
}
@Bean
public UserService userService(){
return new UserServiceImpl(userDao(),mailSender());
}
์ฝ๋๋ฅผ ์ดํด ๋ณด์๋ฉด, ๋จผ์ ํธ๋์ญ์
์ ๋ด๋นํ๋ ์ค๋ธ์ ํธ๊ฐ ์ฌ์ฉ๋จ์ผ๋ก ํธ๋์ญ์
๊ฒฝ๊ณ ์ค์ ์ ํด์ค ๋ค์ ์ค์ ๋น์ฆ๋์ค ๋ก์ง์
UserServiceImpl
์ ์์๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
์ต์ข ์ ์ผ๋ก ๋ค์ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ ์์กด ๊ด๊ณ๊ฐ ๊ตฌ์ฑ๋์๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค.
์ด์ ํด๋ผ์ด์ธํธ๋ UserServiceTx
๋น์ ํธ์ถํ๋ฉด ํธ๋์ญ์
๊ฒฝ๊ณ์ค์ ์ด ๋ ๋น์ฆ๋์ค ๋ก์ง์ ์ฌ์ฉํ ์ ์๊ฒ ๋ฉ๋๋ค.
์ง๊ธ ๊น์ง ํด์๋ ๊ณผ์ ์ ํน์ง์ ์ดํด๋ด ์๋ค.
ํ์ฌ, UserServiceTx
์ UserServiceImpl
์ํ๋ก ๋ถ๋ฆฌ๋์ด ์์ต๋๋ค.
๋ถ๊ฐ ๊ธฐ๋ฅ์ ๋ด๊ณ ์๋ ํด๋์ค์ธ UserServiceTx
๋ ๋ถ๊ฐ๊ธฐ๋ฅ์ธ์ ๋๋จธ์ง ํต์ฌ ๋ก์ง์ ๋ชจ๋ UserServiceImpl
์ ์์ํ๋ ๊ตฌ์กฐ๊ฐ ๋ฉ๋๋ค.
๋ฐ๋ผ์ UserServiceImpl
์ UserServiceTx
์ ์กด์ฌ ์์ฒด๋ฅผ ๋ชจ๋ฅด๋ฉฐ, ๋ถ๊ฐ๊ธฐ๋ฅ์ด ํต์ฌ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๊ตฌ์กฐ๊ฐ ๋ฉ๋๋ค.
๋ฌธ์ ์
ํด๋ผ์ด์ธํธ๊ฐ
UserServiceImpl
๋ฅผ ์ง์ ์ฌ์ฉํด๋ฒ๋ฆฌ๋ฉด?? ๋ถ๊ฐ๊ธฐ๋ฅ์ด ์ ์ฉ๋์ง๋ฅผ ๋ชปํฉ๋๋ค.
๋ฐ๋ผ์, ์ด๋ฌํ ์ฌํ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํ์ฌ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ด์ ํด๋์ค์ธ UserServiceTx
๋ฅผ ๋ง์น ํต์ฌ๊ธฐ๋ฅ์ ๊ฐ์ง ํด๋์ค์ฒ๋ผ ๊พธ๋ฉฐ ํด๋ผ์ด์ธํธ๊ฐ
์ด ํด๋์ค๋ง์ ์ฌ์ฉํ๋๋ก ๋ง๋ค์ด์ผ ํฉ๋๋ค.
๊ทธ๋ฌ๊ธฐ ์ํด์๋ ํด๋ผ์ด์ธํธ๋ ์ง์ ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์๋ ์ธํฐํ์ด์ค๋ฅผ ํตํด์๋ง ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋๋ก ํ์ฌ์ผ ํฉ๋๋ค.
์ฆ, ์ฌ์ฉ์๋ ์ ํฌ๊ฐ ๋ง๋ค์ด๋์
์ด ๊ตฌ์กฐ๋ฅผ ๋ชจ๋ฅด๋๋ผ๋ ์ด๋ฐ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๊ฒ๋ ์ ๋ํ๋ ๊ฒ์
๋๋ค.
ํ์ด์ ์ด์ผ๊ธฐํ์๋ฉด ํด๋ผ์ด์ธํธ๋ ์ธํฐํ์ด์ค๋ง์ ๋ณด๊ณ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์, ๊ทธ ์ธํฐํ์ด์ค๊ฐ ๋น์ฆ๋์ค ๋ก์ง์ ๊ฐ์ง ์ฝ๋์ธ์ค ์ํ ์ง๋ง ์ฌ์ค์ ๋ถ๊ฐ๊ธฐ๋ฅ ์ฝ๋๋ฅผ ํตํด ์ ๊ทผํ๊ฒ ๋๋ ๊ฒ์ ๋๋ค.
๋ด์๋ ๊ฒ์ฒ๋ผ ํด๋ผ์ด์ธํธ๊ฐ ์ฌ์ฉํ๋ ค๊ณ ํ๋ ์ค์ ๋์์ธ๊ฒ ์ฒ๋ผ ์์ฅํด์ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ ๋ฐ์์ฃผ๋ ๊ฒ,
์ง๊ธ์ UserServiceTx
๊ฐ ํ๋ ์ญํ ์ ๋๋ฆฌ์๋ผ๋ ๋ป์ผ๋ก Proxy๋ผ๊ณ ๋ถ๋ฅด๊ฒ ๋ฉ๋๋ค.
์ด๋ฐ ํ๋ก์๋ฅผ ํตํด ์์๋ฐ์ ํต์ฌ๊ธฐ๋ฅ์ ์ฒ๋ฆฌํ๋ ์ค์ ์ค๋ธ์ ํธ๋ฅผ Target์ด๋ผ๊ณ ๋ถ๋ฆ ๋๋ค.
Proxy์ ํน์ง & ์ฌ์ฉ์ด์
1. Target๊ณผ ๊ฐ์ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํ, ํ๋ก์๋ Target์ ์ ์ดํ ์์น์ ์์ผํฉ.
2. ํด๋ผ์ด์ธํธ๊ฐ ํ๊น์ ์ ๊ทผ ๋ฐฉ๋ฒ ์ ์ด.
3. ํ๊น์ ๋ถ๊ฐ๊ธฐ๋ฅ ๋ถ์ฌ ๊ฐ๋ฅ.
๋ฐ์ฝ๋ ์ดํฐ ํจํด์ด๋ ์ผ๋ฐ์ ์ผ๋ก ๋ฐํ์์์ ํ๊น์๊ฒ ๋ถ๊ฐ์ ์ธ ๊ธฐ๋ฅ์ ๋ถ์ฌํ๊ธฐ ์ํด ํ๋ก์๋ฅผ ์ฌ์ฉํ๋ ํจํด์ ๋งํฉ๋๋ค.
์ฆ, ๋ฐํ์ ์์ ์ ๋ถ๊ฐ์ ์ธ ๊ธฐ๋ฅ์ ๋ถ์ฌํ๊ธฐ ๋๋ฌธ์ ์ฝ๋์์์๋ ์ด๋ค ๋ฐฉ์์ผ๋ก ํ๋ก์์ ํ๊น์ด ์ฐ๊ฒฐ๋์ด์๋์ง ์ ์ ์์ต๋๋ค.
๋ฐ์ฝ๋ ์ดํฐ ํจํด์์๋ ๊ฐ์ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํ๊ฒ๊ณผ ์ฌ๋ฌ๊ฐ์ ํ๋ก์๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ์ด๋ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์๋ฅผ ๋ค์ด Transaction๋ฟ๋ง์๋๋ผ
ํ๊ฒ์ ์ฌ๋ฌ๊ฐ์ง์ ๊ธฐ๋ฅ์ ํ ๋ฒ์ ๋ถ์ฌ์ํฌ ์ ์๋ค๋ ๊ฒ์ ๋ปํฉ๋๋ค.
ํ๋ก์๋ก์ ๋์ํ๋ ๊ฐ ๋ฐ์ฝ๋ ์ดํฐ๋ ๋ค์ ๋จ๊ณ๊ฐ ๋ฐ์ฝ๋ ์ดํฐ ํ๋ก์์ธ์ง ์ต์ข
ํ๊ฒ์ธ์ง๋ฅผ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์
๋ค์ ์์ ๋์์ ์ธํฐํ์ด์ค๋ก ์ ์ธํ๋ฉฐ ์ธ๋ถ์์ ๋ฐํ์ ์์ ์ฃผ์
๋ฐ์ ์ ์๋๋ก ํฉ๋๋ค.
์ฆ, ์คํ๋ง์ DI๋ ๋ฐ์ฝ๋ ์ดํฐ ํจํด์ ์ ์ฉ์ํค๊ธฐ์ ์์ฃผ ํธ๋ฆฌํ๋ค๊ณ ํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ์ง๊ธ ๊ตฌ์ฑํ UserService
์ ํธ๋์ญ์
๊ธฐ๋ฅ๊ณผ ๋๋ถ์ด ๋ ๋ค๋ฅธ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ค๊ณ ํ๋ฉด,
@Bean
public UserService userServiceAnother(){
return new UserServiceAnother(userServiceTx());
}
@Bean
public UserService userServiceTx(){
return new UserServiceTx(userService(),transactionManager());
}
@Bean
public UserService userService(){
return new UserServiceImpl(userDao(),mailSender());
}
์ด๋ฐ ์์ผ๋ก ํ์ํ๋ฉด ์ธ์ ๋ ์ง ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ถ๊ฐ์ํฌ ์ ์๊ฒ ๋ฉ๋๋ค.
๋ฐ์ฝ๋ ์ดํฐ ํจํด์ ์ต์ข
ํ๊ฒ์ ๊ธฐ๋ฅ์ ๋ถ์ฌํ๋ ค๋ ๋ชฉ์ ์ผ๋ก ์ค๊ฐ์ ํ๋ก์๊ฐ ๋ผ์ด๋ค์๋ค๋ฉด, ์ผ๋ฐ์ ์ผ๋ก ํ๋ก์ ํจํด์
์ต์ข
ํ๊ฒ์ ๋ํ ์ ๊ทผ ์ ์ด๋ฅผ ์ํด ๋ง๋ค์ด์ง ๊ฒฝ์ฐ๋ฅผ ๋ปํฉ๋๋ค.
๋ ์์ธํ, ํ๋ก์ ํจํด์ ํ๊น์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ ๊ฒ์ด ์๋ ํด๋ผ์ด์ธํธ๊ฐ ํ๊น์ ์ ๊ทผํ๋ ๋ฐฉ๋ฒ์ ๋ฐ๊ฟ์ค๋ค๊ณ ์๊ฐํ์๋ฉด ํธํฉ๋๋ค.
์ฆ, ํ๊ฒ์ ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ๊ธฐ๊ฐ ๋ณต์กํ๊ฑฐ๋ ๋น์ฅ ํ์ํ์ง ์์ ๊ฒฝ์ฐ ๋ฐ๋ก ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์๋ ํ๋ก์๋ฅผ ์์ฑํด ๋์๋ค๊ฐ,
ํด๋ผ์ด์ธํธ๊ฐ ๋ฉ์๋๋ฅผ ์์ฒญํ ์์ ์ ํ๋ก์๊ฐ ํ๊น ์ค๋ธ์ ํธ๋ฅผ ๋ง๋ค๊ณ , ์์ฒญ์ ์์ํด์ฃผ๋ ๊ฒ์
๋๋ค.
๋ง์น JPA์ ํ๋ก์ ํจํด๊ณผ๋ ์ ์ฌํฉ๋๋ค.
JPA ํ๋ก์ ํจํด ๋ณด๋ฌ๊ฐ๊ธฐ (์ฆ์๋ก๋ฉ, ์ง์ฐ๋ก๋ฉ)
๊ตฌ์กฐ์ ์ผ๋ก ๋ณด์๋ฉด, ํ๋ก์ ํจํด ๋ํ ๋ค์ ๋์์ ์ธํฐํ์ด์ค๋ฅผ ํตํด ์์ ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ๋ฐ์ฝ๋ ์ดํฐ ํจํด๊ณผ
์ ์ฌํ๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค. ๋ค๋ง, ๋ฐ์ฝ๋ ์ดํฐ ํจํด์ ๋ค์ ๋์์ด ๋ฌด์์ธ์ง๋ฅผ ๋ชฐ๋ผ๋ ๋์์ง๋ง ํ๋ก์ ํจํด์ ํ๋ก์๋
์ฝ๋์์ ์์ ์ด ๋ง๋ค๊ฑฐ๋ ์ ๊ทผํ ํ๊ฒ์ ์ ๋ณด๋ฅผ ์์์ผํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์๋ํ๋ฉด, ํ๊ฒ์ ์ค๋ธ์ ํธ๋ฅผ ๋ง๋ค์ด์ผ ํ๋ ํ๋ก์์ผ ๊ฒฝ์ฐ
ํ๊ฒ์ ๋ํ ์ง์ ์ ์ธ ์ ๋ณด๋ฅผ ์์์ผํ๊ธฐ ๋๋ฌธ์
๋๋ค.
์ธํฐํ์ด์ค๋ฅผ ํตํด ๋ค์ ๋์์ ์์ํ๋ฏ๋ก ๊ฒฐ๊ตญ์ ๋ฐ์ฝ๋ ์ดํฐ ํจํด๊ณผ ํ๋ก์ ํจํด ๋ ๊ฐ์ง ๊ฒฝ์ฐ๋ฅผ ํผํฉํ์ฌ๋ ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
์ด๋ฐ ๋๋์ผ๋ก ๋ง์ด์ฃ ..
ํ๋ก์๊ฐ ์ด๋ค ์ด์ ๋ก ๋ง๋ค์ด ์ก๋์ง ๋ํ ํ๋ก์๊ฐ ์ด๋ค ๋ฐฉ์์ผ๋ก ๋ง๋ค์ด ์ง๋ ์ง๋ฅผ ์ง๊ธ๊น์ง ์์๋ณด์์ต๋๋ค.
๊ทธ ์ด์ ๋
์ฒซ๋ฒ์งธ๋ก๋, ํ๋ก์๋ฅผ ๊ตฌ์ฑํ๊ณ ๋ ๋ค์ ํ๊น์๊ฒ ์์ํ๋ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ๋ฒ๊ฑฐ๋กญ๋ค๋ ์ ์ ๋๋ค.
์๋ํ๋ฉด, ํด๋ผ์ด์ธํธ๋ ๊ฒฐ๊ตญ์๋ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ด์ฉํ์ฌ ํ๊น์๊ฒ ์ ๊ทผ์ด ๊ฐ๋ฅํ ํฐ์ธ๋ฐ, ํ๊น์ ๋ฉ์๋๊ฐ ๋ง์์ง์๋ก ์์ํด์ค์ผํ๋ ์ฝ๋์ ์์ ๊ธธ์ด์ง ๊ฒ์ด๋ฉฐ,
๊ธฐ๋ฅ์ด ์ถ๊ฐ๊ฑฐ๋ ์์ ๋ ๋ ๋ํ ํจ๊ป ๊ณ ์ณ์ค์ผํ๋ค๋ ๋ฌธ์ ์ ์ด ์์ต๋๋ค.
๋๋ฒ์งธ๋ก๋, ๋ถ๊ฐ๊ธฐ๋ฅ ์ฝ๋ ์์ฑ์ด ์ค๋ณต๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค๋ ์ ์ ๋๋ค. ์๋ํ๋ฉด, ๋ชจ๋ ๋ฉ์๋๋ง๋ค ๋๊ฐ์ด ์ ์ฉ์์ผ์ผ ํ ์ง๋ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ด๋ฐ ๋ฌธ์ ์ ์ ํด๊ฒฐํ ์ ์๋๊ฒ์ด ๋ฐ๋ก Dynamic Proxy์ ๋๋ค.
Dynamic Proxy๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ ์ ๋จผ์ ๋ฆฌํ๋ ์ ์ ๋ํด์ ์์๋ด ์๋ค.
๋ฆฌํ๋ ์ API๋ฅผ ํ์ฉํด ๋ฉ์๋์ ๋ํ ์ ์๋ฅผ ๋ด์ Method ์ธํฐํ์ด์ค๋ฅผ ํ์ฉํด ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ์ ์์๋ด ์๋ค.
ArrayList์ size๋ผ๋ ๋ฉ์๋๋ฅผ ์ถ์ถํ ๋ค invoke๋ฅผ ํตํด ์ถ์ถํด๋ธ ๋ฉ์๋๋ฅผ ์คํ์์ผ ๋ด ์๋ค.
@Test
@DisplayName("Reflect - Method Test")
void ๋ฆฌํ๋ ํธ_๋ฉ์๋_์ถ์ถ_ํ
์คํธ() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method sizeMethod= ArrayList.class.getMethod("size");
List<Integer> testList=new ArrayList<>();
testList.add(1);
testList.add(2);
testList.add(3);
Assertions.assertThat(testList.size()).isEqualTo(3);
Assertions.assertThat(testList.size()).isEqualTo(sizeMethod.invoke(testList));
Assertions.assertThat(sizeMethod.invoke(testList)).isEqualTo(3);
}
ํ ์คํธ ๊ฒฐ๊ณผ
๋ณด์๋ ๊ฒ๊ณผ ๊ฐ์ด Reflect๋ฅผ ํ์ฉํด์ ๋ฉ์๋์ ๋ํ ์ ๋ณด๋ฅผ ์ถ์ถํด๋ผ ์ ์์๊ณ , ์ด๋ฅผ ์ด์ฉํ์ฌ ์ง์ ํ ์ค๋ธ์ ํธ์ ๋ํ์ฌ ๋ฉ์๋๋ฅผ ์คํ์ํฌ ์ ์๋ค๋ ๊ฒ์
ํ์ธํ์์ต๋๋ค.
Dynamic Proxy์ ๋์ ๋ฐฉ๋ฒ๋ถํฐ ์ดํด๋ด ์๋ค.
Dynamic Proxy๋? ๋จผ์ ํ๋ก์ ํฉํ ๋ฆฌ์ ์ํด ๋ฐํ์ ์ ๋ค์ด๋ด๋ฏนํ๊ฒ ๋ง๋ค์ด์ง๋ ํ๋ก์ ์
๋๋ค. ํ๋ก์ ํฉํ ๋ฆฌ์๊ฒ Interface
์ ์ ๋ณด๋ง
๋๊ฒจ์ฃผ๋ฉด ํ๋ก์๋ฅผ ์ ์ฉํ ์ค๋ธ์ ํธ๋ฅผ ์๋์ผ๋ก ๋ง๋ค์ด์ฃผ๊ฒ ๋ฉ๋๋ค.
์ด ๊ณผ์ ์์, ์ถ๊ฐ์ํค๊ณ ์ ํ๋ ๋ถ๊ฐ๊ธฐ๋ฅ์ Invocation Handler
์ ๋ฃ์ด์ฃผ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
InvocationHandler.java
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHancler
์ธํฐํ์ด์ค์
๋๋ค. invoke
๋ผ๋ ๋ฉ์๋๋ ์์์ ์งํํด๋ณด์๋ ๋ฆฌํ๋ ์
API์ Method ์ธํฐํ์ด์ค์ ํ๊น ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ ๋ฐ์ต๋๋ค.
์ฆ, ํด๋ผ๋ฆฌ์ธํธ์ ๋ชจ๋ ์์ฒญ ๋ฉ์๋๋ Dynamic Proxy
๋ฅผ ํตํ์ฌ InvocationHandler
์ Invoke
๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋๋ฉฐ
ํ๊น ๋ฉ์๋์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ์ฉ์์ผ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํดํด์ค๋๋ค.
์ด๋ ์์์ ๋ดค๋ ๋๋ฒ์งธ ๋ฌธ์ ์ ์ธ ์ค๋ณต๋ ์ฝ๋๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค. Invoke
๋ผ๋ ๋ฉ์๋ ํ๋๋ก ํ๊น ์ค๋ธ์ ํธ์ ๋ฉ์๋์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ์ฉ์์ผ ์คํํ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
์ด์ ๋, Transaction ๋ถ๊ฐ๊ธฐ๋ฅ์ Dynamic Proxy๋ฅผ ํตํ์ฌ ์ฝ๋๋ก ์์ฑํด๋ด ์๋ค.
TransactionHandler.java
@RequiredArgsConstructor
public class TransactionHandler implements InvocationHandler {
private final Object target;
private final PlatformTransactionManager transactionManager;
private final String pattern;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().startsWith(pattern))
return invokeWithTransaction(method,args);
return method.invoke(target,args);
}
private Object invokeWithTransaction(Method method,Object[] args) throws Throwable{
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try{
Object invoke = method.invoke(target, args);
transactionManager.commit(status);
return invoke;
}catch(InvocationTargetException e){
transactionManager.rollback(status);
throw e.getTargetException();
}
}
}
์ด ์ฝ๋์์๋
1. Target Object
2. Transaction Manager
3. Method Pattern (๋ถ๊ฐ๊ธฐ๋ฅ์ ์ง์ ๋ ๋ฉ์๋์๋ง ์ ์ฉ์ํค๊ธฐ ์ํด)
๋ค์ DI ์์ผ์ฃผ๋ ๋ถ๋ถ์ ์ ์ํ๋ฉด ๋ฉ๋๋ค.
Dynamic Proxy - Client์์ ์ง์ ์์ฑ
TransactionHandler txHandler=new TranscationHandler();
UserService userService=(UserService)Proxy.newProxyInstance(
getClass().getClassLoader(),new Class[]{UserService.class},txHndler
);
์ด๋ ๊ฒ Dynamic Proxy๋ฅผ ์ง์ ์์ฑํด์ค ์๋ ์์ต๋๋ค.
์ง๊ธ๋ถํฐ๋, TransactionHandler
์ Dynamic Proxy
๋ฅผ ์คํ๋ง DI๋ฅผ ํตํด ์ฌ์ฉํ ์ ์๋๋ก ๋ง๋ค๋ฉด ๋ฉ๋๋ค.
ํ์ง๋ง, Dynamic Proxy
๋ ๋ฐํ์ ์์ ๋์ ์ผ๋ก ๋ง๋ค์ด์ง๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ์ ์ธ ์คํ๋ง Bean์ผ๋ก ๋ฑ๋กํ ์ ์๋ค๋ ๊ฒ์
๋๋ค.
์คํ๋ง์ ์์ฑ์๋ฅผ ํตํด ์ค๋ธ์ ํธ๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ ์ธ์๋ ๋ค์ํ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ๊ทธ ์ค ํ๋๊ฐ ์ด Factory Bean์ ๋๋ค.
Factory Bean ์ ์คํ๋ง์ ๋์ ํด์ ์ค๋ธ์ ํธ์ ์์ฑ๋ก์ง์ ๋ด๋นํ๋๋ก ๋ง๋ค์ด์ง ํน๋ณํ ๋น์ ๋งํฉ๋๋ค.
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
์ด ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก ์ด ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค๋ฅผ ์คํ๋ง์ ๋น์ผ๋ก ๋ฑ๋กํด์ฃผ๋ฉด ๋๋ ๊ฒ์ ๋๋ค.
์ถ๊ฐ๋ก, ์คํ๋ง์ FactoryBean
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค๊ฐ ๋น์ ํด๋์ค๋ก ์ง์ ๋๋ฉด, ํฉํ ๋ฆฌ ๋น ํด๋์ค์ getObject()๋ฅผ ํตํ์ฌ ์ค๋ธ์ ํธ๋ฅผ ๊ฐ์ ธ์ค๊ณ ,
์ด๋ฅผ ๋น ์ค๋ธ์ ํธ๋ก ์ฌ์ฉํฉ๋๋ค. ๋น์ ํด๋์ค๋ก ๋ฑ๋ก๋ ํฉํ ๋ฆฌ๋น์ ๋น ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ๋ ๊ณผ์ ์์๋ง ์ฌ์ฉ๋ฉ๋๋ค.
FactoryBean
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ง๋ค์ด๋๋ฉด getObject() ๋ผ๋ ๋ฉ์๋๊ฐ ์์ฑํด์ฃผ๋ ์ค๋ธ์ ํธ๊ฐ ์ค์ ๋น์
์ค๋ธ์ ํธ๋ก ๋์ฒด ๋๋ค๊ณ ๋ณด์๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ฝ๋๋ฅผ ํตํด ์ดํด๋ด ์๋ค.
TransactionFactoryBean.java
@RequiredArgsConstructor
@Getter
public class TransactionFactoryBean implements FactoryBean<Object> {
private final Object target;
private final PlatformTransactionManager transactionManager;
private final String pattern;
private final Class<?> interfaces;
@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(getClass().getClassLoader(),new Class[]{interfaces},new TransactionHandler(target,
transactionManager,pattern));
}
@Override
public Class<?> getObjectType() {
return interfaces;
}
@Override
public boolean isSingleton() {
return false;
}
}
AppConfig.java - ์คํ๋ง ๋น ๋ฑ๋ก
@Bean
public TransactionFactoryBean userService(){
return new TransactionFactoryBean(userServiceImpl(),transactionManager()
,"upgradeLevels()",UserService.class);
}
์ง๊ธ ๊น์ง, Dynamic Proxy
์ Factory Bean
์ ์ ์ฉํด ๋ณด์์ต๋๋ค. ์ฅ์ ๊ณผ ๋จ์ ๋ํ ์์๋ด
์๋ค.
์ฅ์
Factory Bean
์ ๋ค์ํ ํด๋์ค์ ์ ์ฉ๊ฐ๋ฅํฉ๋๋ค. ๋ํ ํ๋ ์ด์์ ๋น์ ๋ฑ๋กํด๋ ์๊ด ์์ต๋๋ค.๋จ์
Factory Bean
์ ์ค์ ์ ์ค๋ณต์ ๋ง์ ์ ์๋ค๋ ๊ฒ์ ๋ปํฉ๋๋ค.)TransactionHandler
์ค๋ธ์ ํธ๋ FactoryBean
์ ๊ฐ์๋งํผ ๋ง๋ค์ด ์ง๋๋ค. ์์ ์ฝ๋์์ ๋ณด์
จ๋ค ์ํผ ํ๊ฒ์ด ๋ฌ๋ผ์ง ๋๋ง๋ค,TransactionHandler
๋ฅผ ๋ง๋ค์ด ์ค์ผ ํ์ต๋๋ค.๋ค์๋ถํฐ๋, ์ด ๋จ์ ๋ค์ ํด๊ฒฐํด๋๊ฐ ๋ด ์๋ค!
์คํ๋ง์ ์ผ๊ด๋ ๋ฐฉ๋ฒ์ผ๋ก ํ๋ก์๋ฅผ ๋ง๋ค ์ ์๊ฒ ๋์์ฃผ๋ ์ถ์ ๋ ์ด์ด๋ฅผ ์ ๊ณตํฉ๋๋ค. ์คํ๋ง์ ํ๋ก์ ์ค๋ธ์ ํธ๋ฅผ ์์ฑํด์ฃผ๋ ๊ธฐ์ ์
์ถ์ํํ ํ๋ก์ ํฉํ ๋ฆฌ ๋น์ ์ ๊ณตํ์ฌ ์ค๋๋ค.
์คํ๋ง์ ProxyFactoryBean
์ ํ๋ก์๋ฅผ ์์ฑํด์ ๋น ์ค๋ธ์ ํธ๋ก ๋ฑ๋กํ๊ฒ ํด์ฃผ๋ ํฉํ ๋ฆฌ ๋น์ด๋ฉฐ,
์์ํ๊ฒ ํ๋ก์๋ฅผ ์์ฑํ๋ ์์
๋ง๋ค ๋ด๋นํ๊ฒ ๋ฉ๋๋ค.
๋ถ๊ฐ๊ธฐ๋ฅ๊ณผ ๊ฐ์ ์์ ์ ๋ณ๋์ ๋น์ ๋ ์ ์์ต๋๋ค.
ProxyFactoryBean
์ InvocationHandler
๊ฐ ์๋ MethodInterceptor
๋ฅผ ์ฌ์ฉํฉ๋๋ค.
๋์ ๊ฐ์ฅ ํฐ ์ฐจ์ด ์ ์
InvocationHandler
๋ target์ ์ ๋ณด๋ฅผ ์ง์ ์๊ณ ์์ด์ผ Method๋ฅผ Invokeํ ์ ์์๋ ๋ฐ๋ฉด์,
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().startsWith(pattern))
return invokeWithTransaction(method,args);
return method.invoke(target,args);
}
MethodInterceptor
๋ target์ค๋ธ์ ํธ์ ๋ํ ์ ๋ณด๋ ProxyFactoryBean
์๊ฒ ์ ๊ณต๋ฐ๊ธฐ ๋๋ฌธ์, ํ๊น์ ๋ํ ์ ๋ณด๋ฅผ ์ง์ ๋ชฐ๋ผ๋ ๋ฉ๋๋ค.
๋ ๋๋ถ์ MethodInterceptor
๋ ํ๊น๊ณผ ์๊ด ์์ด ๋
๋ฆฝ์ ์ผ๋ก ๋ง๋ค ์ ์์ผ๋ฉฐ, ์ฑ๊ธํค ๋น์ผ๋ก๋ ๋ฑ๋ก์ด ๊ฐ๋ฅํฉ๋๋ค.
์ด์ ๊ฐ์ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํด ๋ด ์๋ค
TransactionAdvice.java
@RequiredArgsConstructor
public class TransactionAdvice implements MethodInterceptor {
private final PlatformTransactionManager transactionManager;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try{
Object ret = invocation.proceed();
transactionManager.commit(status);
return ret;
}catch(RuntimeException e){
transactionManager.rollback(status);
throw e;
}
}
}
AppConfig.java
@Configuration
@RequiredArgsConstructor
@EnableTransactionManagement
public class AppConfig {
private final Environment env;
//Advice ๋ถ๋ถ ์ค๋ช
@Bean
public TransactionAdvice transactionAdvice(){
return new TransactionAdvice(transactionManager());
}
//Pointcut ๋ถ๋ถ ์ค๋ช
//NameMatchMethodPointcut์ ์คํ๋ง ๊ธฐ๋ณธ ์ ๊ณต
@Bean
public NameMatchMethodPointcut transactionPointcut(){
NameMatchMethodPointcut pointcut=new NameMatchMethodPointcut();
pointcut.setMappedNames("upgrade*");
return pointcut;
}
//Advisor = Advice + Pointcut
@Bean
public DefaultPointcutAdvisor transactionAdvisor(){
return new DefaultPointcutAdvisor(transactionPointcut(),transactionAdvice());
}
@Bean
public ProxyFactoryBean userService() {
ProxyFactoryBean factoryBean = new ProxyFactoryBean();
factoryBean.setTarget(userServiceImpl());
factoryBean.setInterceptorNames("transactionAdvisor");
return factoryBean;
}
...
}
์์ ์ฝ๋๋ฅผ ๋ณด์๋ค ์ํผ, ํ๊น์๋ํ ์ ๋ณด๋ฅผ ์ง์ ์ ์ผ๋ก ์๊ณ ์์ง ์์ต๋๋ค. MethodInvocation
์ด๋ผ๋ ํ๋ผ๋ฏธํฐ๋ก ํ๊น์ ๋ํ
์ ๋ณด์ ๋ฉ์๋์ ๋ํ ์ ๋ณด๊ฐ ํจ๊ป ๋์ด์จ๋ค๊ณ ์๊ฐํ์๋ฉด ๋ฉ๋๋ค.
Target์ด ํ์ ์๋ ์์ํ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ปํฉ๋๋ค.
MethodInvocation
์ ๋ฉ์๋ ์ ๋ณด์ ํ๊น ์ค๋ธ์ ํธ๊ฐ ๋ด๊ฒจ์๋ ํ๋ผ๋ฏธํฐ์
๋๋ค.
MethodInvocation
์ ํ๊น ์ค๋ธ์ ํธ์ ๋ฉ์๋๋ฅผ ์คํํ ์ ์๋ ๊ธฐ๋ฅ์ด ์๊ธฐ ๋๋ฌธ์ MethodInterceptor
๋ ๋ถ๊ฐ๊ธฐ๋ฅ์๋ง
์ง์ค์ ํ ์ ์์ต๋๋ค.
MethodInvocation
์ proceed() ๋ฉ์๋๋ฅผ ์คํํ๋ฉด ํ๊ฒ ์ค๋ธ์ ํธ์ ๋ฉ์๋๋ฅผ ๋ด๋ถ์ ์ผ๋ก ์คํํด์ฃผ๋ ๊ธฐ๋ฅ์ด ์์ต๋๋ค.
์ฆ, MethodInvocation
์ ๊ตฌํํ ํด๋์ค๋ฅผ ํด๋์ค๊ฐ ๊ณต์ ๊ฐ๋ฅํ๊ฒ ์ฌ์ฉ๊ฐ๋ฅํ๋ค๋ ๊ฒ์
๋๋ค.
๊ทธ๋ฅ JDK์์์ ProxyFactoryBean
์ ๋จ์ ์ด์๋ TransactionHandler
์ค๋ธ์ ํธ๋ FactoryBean
์ ๊ฐ์๋งํผ ๋ง๋ค์ด ์ง๋๋ค. ์์ ์ฝ๋์์ ๋ณด์
จ๋ค ์ํผ ํ๊ฒ์ด ๋ฌ๋ผ์ง ๋๋ง๋ค,
๊ณตํต ๊ธฐ๋ฅ์์๋ ๋ถ๊ฐํ๊ณ ์๋ก TransactionHandler
๋ฅผ ๋ง๋ค์ด ์ค์ผ ํ์ต๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๊ฒ ๋์์ต๋๋ค.
๋ํ, MethodInterceptor
๋ฅผ ๊ตฌํํ TransactionAdvice
์ ์ด๋ฆ์์ ์ ์ ์๋ฏ์ด
ํ๊ฒ ์ค๋ธ์ ํธ์ ์ ์ฉํ๋ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ด์ ์ค๋ธ์ ํธ๋ฅผ ์คํ๋ง์์๋ ์ด๋๋ฐ์ด์ค(Advice)๋ผ๊ณ ๋ถ๋ฅด๊ฒ ๋ฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก ๋ค๋ฅธ ์ ์ด ์์ต๋๋ค.
TransactionFactoryBean
์ ์ฌ์ฉํ์ ๋๋ Dynamic Proxy
๋ฅผ ๋ง๋ค๊ธฐ ์ํด์ ์ธํฐํ์ด์ค ํ์
์ ์ ๊ณต๋ฐ์์ผ ํ์ต๋๋ค.
//TransactionFactoryBean
@RequiredArgsConstructor
@Getter
public class TransactionFactoryBean implements FactoryBean<Object> {
private final Object target;
private final PlatformTransactionManager transactionManager;
private final String pattern;
private final Class<?> interfaces; //์ด ๋ถ๋ถ
...
}
ํ์ง๋ง, ์ฐ๋ฆฌ๊ฐ ๊ตฌํํ Advice
์์๋ ๋ฐ๋ก ์ธํฐํ์ด์ค์ ์ ๋ณด๋ฅผ ์ ๊ณต๋ฐ์ง ์์๋ ๋์์ต๋๋ค. ๊ทธ ์ด์ ๋,
์ธํฐํ์ด์ค์ ์ ๋ณด๋ฅผ ์ ๊ณตํ์ง ์์๋ ProxyFactoryBean
์๋ ์ธํฐํ์ด์ค๋ฅผ ์๋ ๊ฒ์ถํ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ํ๊ฒ ์ค๋ธ์ ํธ๊ฐ
๊ตฌํํ๊ณ ์๋ ์ธํฐํ์ด์ค ์ ๋ณด๋ฅผ ์์๋ด๊ธฐ ๋๋ฌธ์
๋๋ค.
์ด๋ ๊ฒ Advice์ ๋ํด์ ์์๋ณด์์ต๋๋ค. Advice๋ ํ๊ฒ ์ค๋ธ์ ํธ์ ์์ํ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ด์ ์ค๋ธ์ ํธ๋ผ๊ณ ์์๋ฉด ๋ฉ๋๋ค.
๋ถ๊ฐ๊ธฐ๋ฅ ์ ์ฉ๋์ ๋ฉ์๋ ์ ์ ๋ฐฉ๋ฒ์ ๋ปํฉ๋๋ค.
InvocationHandler
๋ฅผ ๊ตฌํํTransactionHandler
์์๋ String ๊ฐ์ผ๋ก Pattern์ ์ฃผ์
๋ฐ์ ๋ถ๊ฐ๊ธฐ๋ฅ์ด ์ ์ฉ๋ ๋์ ๋ฉ์๋๋ฅผ ์ ์ ํ์์ต๋๋ค.
๊ทธ๋ ๋ค๋ฉด MethodInterceptor
์์๋ ๋๊ฐ์ด pattern์ ์ฃผ์
๋ฐ์ ๋ด๋ถ ๋ก์ง์ผ๋ก ์ฒ๋ฆฌํ๋ฉด ๋ ๊น์?? ์๋๋๋ค!!
MethodInterceptor
๋ ์ฌ๋ฌ ํ๋ก์์์ ๊ณต์ ํด์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ๋ง์ ์ฆ, ํ๊ฒ์ ๋ํ ์ ๋ณด๋ฅผ ์ง์ ์ ์ผ๋ก ๊ฐ์ง๊ณ ์์ง ์๋ค๋ ๋ป๊ณผ ๊ฐ์ต๋๋ค.
๋๋ฌธ์, ์ฑ๊ธํคํํ์ธ ์คํ๋ง ๋น์ผ๋ก๋ ๋ฑ๋กํ ์ ์์๋ ๊ฒ ์
๋๋ค.
๋ ์์ธํ ๋ณด์๋ฉด, InvocationHandler
๋ฐฉ์์ ๋ฌธ์ ์ ์ด์๋ InvocationHandler
๋ฅผ ๊ตฌํํ ํด๋์ค๊ฐ FactoryBean
์ ๋ง๋ค ๋๋ง๋ค ์๋ก์ด ์ค๋ธ์ ํธ๊ฐ ์์ฑ
๋๋ค๋ ๊ฒ์ด์์ต๋๋ค. ๊ทธ ์ด์ ๋ ํ๊ฒ๋ง๋ค ๋ฉ์๋ ์ ์ ์๊ณ ๋ฆฌ์ฆ์ด๋ ํ๊ฒ ์์ฒด๊ฐ ๋ค๋ฅผ ์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ค ํ๊ฒ์ด๋, ํด๋์ค์ ์ข
์๋์ง ์๊ธฐ ์ํด์ ์
๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ๊ธฐ๊ป ํ๋ฅญํ ํด๊ฒฐํด ๋จ๋๋ฐ Pattern์ ์ฃผ์
๋ฐ์ ํ์ฉํ๋ค๋ฉด ๋๋ค์ ์ด๋ค ๋ฉ์๋๋ ํด๋์ค์๋ง ์ข
์๋ ์ ๋ฐ์ ์๋ค๋ ๊ฒ์
์๋ฏธํฉ๋๋ค.
์ด๋ฐ ๋ฌธ์ ์ ์ ํด๊ฒฐํ๊ธฐ ์ํด์, ์คํ๋ง์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ์ค๋ธ์ ํธ์ธ Advice์ ๋ฉ์๋ ์ ์ ์๊ณ ๋ฆฌ์ฆ ์ค๋ธ์ ํธ์ธ Pointcut์ ๋ฐ๋ก ๋๋์์ต๋๋ค.
Advice์ Pointcut์ ๋ชจ๋ ์ฃผ์
์ ๋ฐ์ ์ฌ์ฉํ๋ฉฐ, ๋ ๊ฐ์ง ๋ชจ๋ ์ฌ๋ฌ ํ๋ก์์์ ๊ณต์ ๊ฐ ๊ฐ๋ฅํ๋๋ก ๋ง๋ค์ด์ง๊ธฐ ๋๋ฌธ์ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ ์ ์์ต๋๋ค.
์ด์ , ํ๋ก์๋ ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ์์ฒญ์ ๋ฐ์ผ๋ฉด ๋จผ์ Pointcut์๊ฒ ์ ์ฉ ๊ฐ๋ฅํ ๋ฉ์๋์ธ์ง ํ์ธ์ ํ ๋ค, Advice๋ฅผ ํธ์ถํด ์ฃผ๋ฉด ๋ฉ๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก, Advice์ Pointcut์ ๋์
์ผ๋ก ์ธํด ์ฌ๋ฌ ํ๋ก์๊ฐ ๊ณต์ ํ๋ฉฐ ์ ์ฐํ๊ฒ ์ฌ์ฉํ ์ ์๊ฒ ๋์๊ณ , ๊ตฌ์ฒด์ ์ธ ๋ถ๊ฐ๊ธฐ๋ฅ ๋ฐฉ์น์ด๋ ๋ฉ์๋ ์ ์ ์๊ณ ๋ฅด์ง์ด ๋ฐ๋๊ฒ ๋๋ฉด
Advice๋ Pointcut๋ง ๋ฐ๊ฟ์ฃผ๋ฉด ํด๊ฒฐ๋๊ฒ ๋์์ต๋๋ค.
OCP : Open Closed Priciple
์ ์ ์ง์ผฐ๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค.
Advisor๋ Advice์ Pointcut์ ๋ฌถ๋๋ค๊ณ ๋ณด์๋ฉด ๋ฉ๋๋ค.
๋ฌถ๋ ์ด์ ๋, ProxyFactoryBean
์ ์ฌ๋ฌ๊ฐ์ง Advice์ Pointcut์ด ์ถ๊ฐ ๋ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์, ๊ฐ๊ฐ์ Advice๋ง๋ค ๋ฉ์๋๋ฅผ ์ ์ ํ๋ ๋ฐฉ์์ด ๋ฌ๋ผ์ง ์๋ ์์ผ๋ ์ด๋ค Pointcut์ ์ ์ฉํ ์ง ์ ๋งคํด์ง ์ ์์๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ Advice์ Pointcut์ ํ๋๋ก
๋ฌถ์ด์ ์ฌ์ฉํฉ๋๋ค.
์ง๊ธ๊น์ง ํด์๋ ๋ฐ์ ๊ธฐ์ ์ ๋ค์ ํ๋ฒ ์ดํด ๋ด ์๋ค.
Service
๋ก์ง์์ Transaction
๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ถ๋ฆฌ๋ฅผ ์ํด DynamicProxy
์ FactoryBean
์ ๋์
ํ์์ต๋๋ค.Factory Bean
์ ์ค์ ์ ์ค๋ณต์ ๋ง์ ์ ์๋ค๋ ๊ฒ์ ๋ปํฉ๋๋ค.)TransactionHandler
์ค๋ธ์ ํธ๋ FactoryBean
์ ๊ฐ์๋งํผ ๋ง๋ค์ด ์ง๋๋ค. ์์ ์ฝ๋์์ ๋ณด์
จ๋ค ์ํผ ํ๊ฒ์ด ๋ฌ๋ผ์ง ๋๋ง๋ค,TransactionHandler
๋ฅผ ๋ง๋ค์ด ์ค์ผ ํ์ต๋๋ค.SpringProxyFactoryBean
์ ์ฌ์ฉํ์ต๋๋ค.์ด์ ๊ฐ์ ๊ณผ์ ์ผ๋ก ํฌ๋ช
ํ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ์ฉํ ์ ์์๊ณ , ํ๊ฒ์๋ ๋น์ฆ๋์ค ๋ก์ง๋ง ์ ์งํ ์ฑ๋ก ๋ ์ ์์์ต๋๋ค.
๋ํ, ๋ถ๊ฐ๊ธฐ๋ฅ์ ํ ๋ฒ๋ง ๋ง๋ค์ด ๋ชจ๋ ํ๊ฒ๊ณผ ๋ฉ์๋์์ ์ฌ์ฌ์ฉ์ด ๊ฐ๋ฅํ ์ ์๋๋ก ํด๋จ์ต๋๋ค.
But, ํ๊ฐ์ง ๋ฌธ์ ์ ์ด ๋ ๋จ์์ต๋๋ค.
๊ทธ๊ฒ์ ๋ฐ๋ก ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ์ฉ์ด ํ์ํ ํ๊ฒ ์ค๋ธ์ ํธ๋ง๋ค ๊ฑฐ์ ๋น์ทํ ๋ด์ฉ์ ProxyFactoryBean
๋น ์ค์ ์ ๋ณด๋ฅผ ์ถ๊ฐํด์ฃผ๋
๋ถ๋ถ์
๋๋ค.
@Configuration
@RequiredArgsConstructor
@EnableTransactionManagement
public class AppConfig {
private final Environment env;
//์ด ๋ถ๋ถ์ด ๊ณ์ํด์ ๋์ด๋๊ฒ ๋ฉ๋๋ค.
@Bean
public ProxyFactoryBean userService() {
ProxyFactoryBean factoryBean = new ProxyFactoryBean();
factoryBean.setTarget(userServiceImpl());
factoryBean.setInterceptorNames("transactionAdvisor");
return factoryBean;
}
...
}
์์ ๊ฐ์ ์ฝ๋๊ฐ ๊ณ์ํด์ ๋์ด๋๊ฒ ๋ฉ๋๋ค. ๋ฌผ๋ก , ๋จ์ํ๊ณ ์ฌ์ด ๊ณผ์ ์ด์ง๋ง ๋ง์ฝ ์ ๋ฌํ ์ค๋ธ์ ํธ๊ฐ ์ ๋ฐฑ๊ฐ๊ฐ ๋๊ณ ์ด๋ ๊ฒ ๋๋ฉด
๊ต์ฅํ ๋ฒ๊ฑฐ๋ก์ด ์์
์ผ ๋ฟ๋๋ฌ ์ค์ํ๊ธฐ๋ ์ฝ๊ฒ ๋ฉ๋๋ค.
์ฆ, ํ ๋ฒ์ ์ฌ๋ฌ ๊ฐ์ ๋น์ ํ๋ก์๋ฅผ ์ ์ฉํด์ผํฉ๋๋ค!
๋จผ์ , ๋น ํ์ฒ๋ฆฌ๊ธฐ๋ ์ด๋ฆ ๊ทธ๋๋ก ์คํ๋ง ๋น ์ค๋ธ์ ํธ๋ก ๋ง๋ค์ด์ง๊ณ ๋ ํ์, ๋น ์ค๋ธ์ ํธ๋ฅผ ๋ค์ ๊ฐ๊ณตํ ์ ์๊ฒ ํด์ฃผ๋ ๊ฒ์ ๋๋ค.
์ฌ๊ธฐ์ ์ดํด๋ณผ ๊ฒ์ ๋น์ด ์์ฑ๋ ์ดํ์ Advisor๋ฅผ ์ด์ฉํ ์๋ ํ๋ก์ ์์ฑ๊ธฐ์ธ DefaultAdvisorAutoProxyCreator
๋ฅผ ์ดํด๋ณผ ์ ์์ต๋๋ค.
DefaultAdvisorAutoProxyCreator
๊ฐ ๋น ํ์ฒ๋ฆฌ๊ธฐ๋ก ๋ฑ๋ก๋์ด ์์ผ๋ฉด ์คํ๋ง์ ๋น ์ค๋ธ์ ํธ๋ฅผ ๋ง๋ค ๋ ๋ง๋ค ํ์ฒ๋ฆฌ๊ธฐ์๊ฒ ๋น์ ๋ณด๋
๋๋ค.
๊ทธ ์ดํ, ๋น ํ์ฒ๋ฆฌ๊ธฐ๋ ๋น์ผ๋ก ๋ฑ๋ก๋ ๋ชจ๋ Advisor๋ด์ ํฌ์ธํธ์ปท์ ์ด์ฉํด ์ ๋ฌ๋ฐ์ ๋น์ด ํ๋ก์ ์ ์ฉ ๋์์ธ์ง ํ์ธํฉ๋๋ค.
ํ๋ก์ ์ ์ฉ ๋์์ด๋ผ๋ฉด, ๋ด์ฅ๋ ํ๋ก์ ์์ฑ๊ธฐ์๊ฒ ํ์ฌ ๋น์ ๋ํ ํ๋ก์๋ฅผ ๋ง๋ค๊ฒ ํ๊ณ , ๋ง๋ค์ด์ง ํ๋ก์์ Advisor๋ฅผ ์ฐ๊ฒฐํด์ค๋๋ค.
์ด์ , ํ๋ก์๊ฐ ์์ฑ๋๋ฉด ์๋ ์ปจํ ์ด๋๊ฐ ์ ๋ฌํด์ค ๋น ์ค๋ธ์ ํธ ๋์ ํ๋ก์ ์ค๋ธ์ ํธ๋ฅผ ์ปจํ ์ด๋์๊ฒ ๋๋ ค์ฃผ๊ฒ ๋ฉ๋๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก ์ปจํ ์ด๋๋ ํ๋ก์ ์ค๋ธ์ ํธ๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํ๊ณ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
์์ ์ค๋ช
์ ํ ๋๋ก ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ถ์ฌํ ๋น์ ์ ์ฅํ๋ Pointcut์ด ์ฐ๊ฒฐ๋ Advisor๋ฅผ ๋ฑ๋กํ๊ณ , ๋น ํ์ฒ๋ฆฌ๊ธฐ๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค๋ฉด
๋ณต์กํ ์ค์ ์ ๋ณด๋ฅผ ์ ์ ํ์์์ด ์๋์ผ๋ก ํ๋ก์๋ฅผ ์์ฑํ ์ ์๊ฒ ๋ฉ๋๋ค.
Pointcut์ ํ์ฅ
์คํ๋ง ProxyFactoryBean
์ ํ ๋ Pointcut์์๋ ๋ฉ์๋๋ฅผ ์ด๋ป๊ฒ ํ์ ํ ์ง๋ง ์๊ฐํ์ต๋๋ค. ๊ทผ๋ฐ ๋น ํ์ฒ๋ฆฌ๊ธฐ์์ ์ค๋ช
ํ ๋ฐ๋ก๋
Pointcut์ผ๋ก ์ด๋ค ๋น์ด ์ ์ ๋์์ด ๋ ๊ฒ์ธ์ง ๊ตฌ๋ณํด์ผํ๋ค๊ณ ํ๊ณ ์์ต๋๋ค.
์ด๋ป๊ฒ ๋ ๊ฒ์ผ ๊น์??
Pointcut์ ๊ธฐ๋ฅ์ผ๋ก๋ ์๋ ๋ฉ์๋ ์ ์ ๊ธฐ๋ฅ๋ง ์๋ ๊ฒ์ด ์๋ Class Filter๋ํ ๋ฉ์๋๋ก ๊ฐ๊ณ ์์ต๋๋ค.
์ฆ, Pointcut์ ํ๋ก์๋ฅผ ์ ์ฉํ ํด๋์ค์ธ์ง ํ๋จ์ ํ๊ณ ๋์, ์ ์ฉ๋์ ํด๋์ค์ ๊ฒฝ์ฐ์๋ Advice๋ฅผ ์ ์ฉํ ๋ฉ์๋์ธ์ง ํ์ธํ๋ ๋ฐฉ๋ฒ์ผ๋ก ๋์ํฉ๋๋ค.
๊ฒฐ๊ตญ์ ์ด ๋์กฐ๊ฑด ๋ชจ๋๋ฅผ ๋ง์กฑํ๋ ํ๊ฒ์๊ฒ๋ง ๋ถ๊ฐ๊ธฐ๋ฅ์ด ๋ถ์ฌ๋๋ ๊ฒ์
๋๋ค.
์์ ์์๋ณด์๋ ๋น ํ์ฒ๋ฆฌ๊ธฐ์ธ DefaultAdvisorAutoCreator
์์๋ ํด๋์ค์ ๋ฉ์๋ ์ ์ ์ด ๋ชจ๋ ๊ฐ๋ฅํ Pointcut์ด ํ์ํฉ๋๋ค.
NameMatchMethodPointcut
์์์ ๋ฑ๋กํ์๋ Pointcut์ ์ดํด๋ด
์๋ค.
@Bean
public NameMatchMethodPointcut transactionPointcut(){
NameMatchMethodPointcut pointcut=new NameMatchMethodPointcut();
pointcut.setMappedNames("upgrade*");
return pointcut;
}
์์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋ฑ๋ก์ ํ์์ต๋๋ค. ์ฌ๊ธฐ์ ์คํ๋ง์ด ๊ธฐ๋ณธ ์ ๊ณตํ๋ NameMethodPointcut
์
๋ฉ์๋ ์ ์ ๊ธฐ๋ฅ๋ง์ ๊ฐ๊ณ ์์ ๋ฟ, ํด๋์ค ํํฐ์ ๊ธฐ๋ฅ์ ์กด์ฌํ์ง ์์ต๋๋ค.
๋ฐ๋ผ์, ์ด ํด๋์ค๋ฅผ ํ์ฅํ์ฌ ํด๋์ค ํํฐ์ ๊ธฐ๋ฅ์ผ๋ก์๋ ์๋ํ๋๋ก ๋ง๋ค์ด ๋ด ์๋ค.
NameMathClassMethodPointcut.java
public class NameMatchClassMethodPointcut extends NameMatchMethodPointcut {
public void setMappedClassName(String mappedClassName){
this.setClassFilter(new SimpleFilter(mappedClassName));
}
@RequiredArgsConstructor
static class SimpleFilter implements ClassFilter{
private final String mappedName;
@Override
public boolean matches(Class<?> clazz) {
return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName());
}
}
}
public void setMappedClassName(String mappedClassName){
this.setClassFilter(new SimpleFilter(mappedClassName));
}
์ฌ๊ธฐ์ setClassFilter
๊ฐ ์ด๋ป๊ฒ ๋์๋์ง ์์ํ์ค์๋ ์์ต๋๋ค.
์๋๋ฉด, NameMatchMethodPointcut
๋ํ Pointcut
์ด๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ implementsํ๊ธฐ ๋๋ฌธ์
๋๋ค.
public interface Pointcut {
Pointcut TRUE = TruePointcut.INSTANCE;
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut {
private ClassFilter classFilter;
public StaticMethodMatcherPointcut() {
this.classFilter = ClassFilter.TRUE;
}
public void setClassFilter(ClassFilter classFilter) {
this.classFilter = classFilter;
}
public ClassFilter getClassFilter() {
return this.classFilter;
}
public final MethodMatcher getMethodMatcher() {
return this;
}
}
public class NameMatchMethodPointcut extends StaticMethodMatcherPointcut implements Serializable {
private List<String> mappedNames = new ArrayList();
public NameMatchMethodPointcut() {
}
public void setMappedName(String mappedName) {
this.setMappedNames(mappedName);
}
public void setMappedNames(String... mappedNames) {
this.mappedNames = new ArrayList(Arrays.asList(mappedNames));
}
public NameMatchMethodPointcut addMethodName(String name) {
this.mappedNames.add(name);
return this;
}
public boolean matches(Method method, Class<?> targetClass) {
Iterator var3 = this.mappedNames.iterator();
String mappedName;
do {
if (!var3.hasNext()) {
return false;
}
mappedName = (String)var3.next();
} while(!mappedName.equals(method.getName()) && !this.isMatch(method.getName(), mappedName));
return true;
}
protected boolean isMatch(String methodName, String mappedName) {
return PatternMatchUtils.simpleMatch(mappedName, methodName);
}
public boolean equals(@Nullable Object other) {
return this == other || other instanceof NameMatchMethodPointcut && this.mappedNames.equals(((NameMatchMethodPointcut)other).mappedNames);
}
public int hashCode() {
return this.mappedNames.hashCode();
}
public String toString() {
return this.getClass().getName() + ": " + this.mappedNames;
}
}
์ด๋ฐ ์์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. ์ด๋ก์จ, ํด๋์ค ํํฐ๊ธฐ๋ฅ ๋ํ ๊ฐ์ง Pointcut์ ์์ฑ ์๋ฃํ์์ต๋๋ค.
์ด์ , Advisor๋ฅผ ์ด์ฉํ๋ ์๋ ํ๋ก์ ์์ฑ๊ธฐ๊ฐ ์ด๋ค ์์๋ก ์๋์ ํ๋์ง ์์๋ณด๊ณ ์ฝ๋๋ฅผ ์์ฑํด๋ด ์๋ค.
DefaultAdvisorAutoProxyCreator
๋ ๋ฑ๋ก๋ ๋น ์ค์์ Advisor์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ๊ฒ์ ๋ชจ๋ ์ฐพ์ต๋๋ค.๐ ์ฐธ๊ณ ๋ก! DefaultAdvisorAutoProxyCreator
๋ ๋น์ผ๋ก๋ง ๋ฑ๋กํด ๋์๋ฉด ๋ฉ๋๋ค.
AppConfig.java
@Configuration
@RequiredArgsConstructor
@EnableTransactionManagement
public class AppConfig {
private final Environment env;
@Bean
public DataSource dataSource(){
SimpleDriverDataSource dataSource=new SimpleDriverDataSource();
dataSource.setDriverClass(org.h2.Driver.class);
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(dataSource());
}
@Bean
public JdbcOperations jdbcOperations(){
return new JdbcTemplate(dataSource());
}
@Bean
public UserDao userDao(){
return new UserDaoImpl(jdbcOperations());
}
@Bean
public TransactionAdvice transactionAdvice(){
return new TransactionAdvice(transactionManager());
}
@Bean
public NameMatchClassMethodPointcut transactionPointcut(){
NameMatchClassMethodPointcut pointcut=new NameMatchClassMethodPointcut();
pointcut.setMappedClassName("*ServiceImpl");
pointcut.setMappedNames("upgrade*");
return pointcut;
}
@Bean
public DefaultPointcutAdvisor transactionAdvisor(){
return new DefaultPointcutAdvisor(transactionPointcut(),transactionAdvice());
}
//๋น ํ์ฒ๋ฆฌ๊ธฐ ๋ฑ๋ก
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
return new DefaultAdvisorAutoProxyCreator();
}
@Bean
public UserService userService(){
return new UserServiceImpl(userDao(),mailSender());
}
}
์ฝ๋ ์์ฑ๋ํ ์๋ฃํ์ผ๋ฉฐ, ๋น ํ์ฒ๋ฆฌ๊ธฐ๋ฅผ ํตํ ๋ถ๊ฐ๊ธฐ๋ฅ ๋ถ์ฌ ๋ํ ์๋ฒฝํ ์ํํ์์ต๋๋ค.
์ง๊ธ๊น์ง ๋ฐ์ ํด์จ ๊ณผ์ ์ ์ดํด๋ณด๋ฉด, ์ผ์ผ์ด ํด๋์ค ํํฐ์ ๋ฉ์๋ ๋งค์ฒ๋ฅผ ๊ตฌํํ๊ฑฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์คํ๋ง์ด ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํด์์ต๋๋ค.
์ง๊ธ๊น์ง๋ ๋จ์ํ ํด๋์ค ์ด๋ฆ์ด๋ ๋ฉ์๋ ์ด๋ฆ์ ๋น๊ตํ๋ ๊ฒ์ด ์ ๋ถ์๋ค๋ฉด, ์ผ์ข ์ ํํ์ ์ธ์ด๋ฅผ ์ฌ์ฉํ์ฌ ์ข ๋ ์ธ๋ฐํ๊ฒ ์ ์ ์๊ณ ๋ฆฌ์ฆ์ ์งค ์ ์์ต๋๋ค.
์ด๋ ๊ฒ ๊ณ ์๋ ๊ฒ์ด ํฌ์ธํธ์ปท ํํ์์ด๋ผ๊ณ ํฉ๋๋ค.
ํฌ์ธํธ์ปท ํํ์์ AspectJExpressionPointcut
ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
NameMatchClassMethodPointcut
์ ํด๋์ค์ ๋ฉ์๋์ ์ด๋ฆ์ ๊ฐ๊ฐ ๋
๋ฆฝ์ ์ผ๋ก ๋น๊ตํ ๋ฐ๋ฉด์, ํํ์์ผ๋ก๋ ํ๋ฒ์ ์ง์ ๊ฐ๋ฅํ๊ฒ ํด์ค๋๋ค.
AspectJExpressionPointcut
์ ์ด๋ฆ์ ๋ณด๋ค์ํผ, ์คํ๋ง์ AspectJ
๋ผ๋ ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ ๊ฒ์ ์ฌ์ฉํ๊ฒ ๋๋ฉฐ, ์ด๊ฒ์ AspectJํํ์์ด๋ผ ๋ถ๋ฆ
๋๋ค.
AspectJ ํฌ์ธํธ์ปท ํํ์์ ํฌ์ธํธ์ปท ์ง์์๋ฅผ ์ด์ฉํด ์์ฑํฉ๋๋ค. ํฌ์ธํธ์ปท ์ง์์์ค์์ ๊ฐ์ฅ ๋ํ์ ์ผ๋ก ์ฌ์ฉ๋๋ ๊ฒ์ execution์ ๋๋ค.
execution(์ ๊ทผ์ ํ์ ํ์
ํจํด:return ํ์
ํ์
ํจํด:ํด๋์ค ํ์
.์ด๋ฆํจํด(๋ฉ์๋) (ํ์
ํจํด:ํ๋ผ๋ฏธํฐํจํด) throws ์์ธํจํด)
๋ฌธ๋ฒ์ ๋ํ ์์ธํ ์ค๋ช ์ ์๋ตํ๋๋ก ํ๊ฒ ์ต๋๋ค!
์ด์ ๋ Pointcutํํ์์ AspectJ ๋ฉ์๋์ ํ๋ฆฌ๋ฏธํฐ๋ก ํ๋ฉด ์คํ ๊ฐ๋ฅํ๊ฒ ๋ฉ๋๋ค.
Pointcut์ ์ ์ฉํด ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
ํฌ์ธํธ์ปท ํํ์์๋ ์์์ ์ ์ ์ธ๊ธํ๋ execution ์ธ์๋ bean์ ์ ํํ์ฌ์ฃผ๋ bean()๋ฉ์๋, ๋ํ ํน์ ์ ๋
ธํ
์ด์
์ด ํ์
, ๋ฉ์๋, ํ๋ผ๋ฏธํฐ์ ์ ์ฉ๋์ด ์๋ ๊ฒ์ ๋ณด๊ณ
๋ฉ์๋๋ฅผ ์ ์ ํ๊ฒ ํ๋ ํฌ์ธํธ์ปท์ ๋ง๋ค ์ ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด @Transactional
๊ณผ ๊ฐ์ ๊ฒฝ์ฐ๋ฅผ ๋งํฉ๋๋ค.
ํฌ์ธํธ์ปท ํํ์์ AspectJExpressionPointcut๋น์ ๋ฑ๋กํ๊ณ expression ํ๋กํผํฐ์ ๋ฃ์ด์ฃผ๋ฉด ๋ฉ๋๋ค. ํด๋์ค์ด๋ฆ์ ServiceImpl๋ก ๋๋๊ณ ๋ฉ์๋ ์ด๋ฆ์
upgrade๋ก ์์ํ๋ ๋ชจ๋ ํด๋์ค์ ์ ์ฉ๋๋๋ก ์ฝ๋๋ฅผ ์ง๋ด
์๋ค.
AppConfig.java
@Configuration
@RequiredArgsConstructor
@EnableTransactionManagement
public class AppConfig {
private final Environment env;
@Bean
public DataSource dataSource(){
SimpleDriverDataSource dataSource=new SimpleDriverDataSource();
dataSource.setDriverClass(org.h2.Driver.class);
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(dataSource());
}
@Bean
public JdbcOperations jdbcOperations(){
return new JdbcTemplate(dataSource());
}
@Bean
public UserDao userDao(){
return new UserDaoImpl(jdbcOperations());
}
@Bean
public TransactionAdvice transactionAdvice(){
return new TransactionAdvice(transactionManager());
}
//์ถ๊ฐ๋ ๋ถ๋ถ
@Bean
public AspectJExpressionPointcut transactionPointcut(){
AspectJExpressionPointcut pointcut=new AspectJExpressionPointcut();
pointcut.setExpression("bean(*Service)");
return pointcut;
}
@Bean
public DefaultPointcutAdvisor transactionAdvisor(){
return new DefaultPointcutAdvisor(transactionPointcut(),transactionAdvice());
}
//๋น ํ์ฒ๋ฆฌ๊ธฐ ๋ฑ๋ก
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
return new DefaultAdvisorAutoProxyCreator();
}
@Bean
public UserService userService(){
return new UserServiceImpl(userDao(),mailSender());
}
}
์ง๊ธ๊น์ง์ ๊ณผ์ ์ ๊ฑฐ์ณ AspectJExpressionPointcut
์ ์ ์ฉ๊น์ง ์๋ฃํ์ต๋๋ค.
์ผ๋ฐ์ ์ธ ๊ฐ์ฒด์งํฅ ๊ธฐ์ ๋ฐฉ๋ฒ์ผ๋ก๋ ๋
๋ฆฝ์ ์ธ ๋ชจ๋ํ๊ฐ ๋ถ๊ฐ๋ฅํ๊ฒ ๋ฉ๋๋ค. ๋ฐ๋ผ์, ๋ถ๊ฐ๊ธฐ๋ฅ ๋ชจ๋ํ ์์
์
๊ธฐ์กด์ ๊ฐ์ฒด์งํฅ ์ค๊ณ์๋ ๋ค๋ฅธ์๋ฏธ๊ฐ ์๋ค๋ ๋ป์ ๋ฐ์๋ค์ฌ Aspect
๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ถ๊ฐ๊ธฐ๋ฅ ๋ชจ๋ํ ์์
์ ๋ป๋ผ๊ฒ ๋์์ต๋๋ค.
Aspect
๋ ํต์ฌ ๋น์ฆ๋์ค๋ก์ง ๋ฐ ๊ธฐ๋ฅ์ ๊ฐ์ง๊ณ ์์ง๋ ์์ง๋ง ์ง๊ธ๊น์ง ํด์๋ ํธ๋์ญ์
๊ฒฝ๊ณ ์ค์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ค๋์ง ํต์ฌ๊ธฐ๋ฅ์ ๋ถ๊ฐ๋๋ ๋ชจ๋์ ์ง์นญํฉ๋๋ค.
์ด๋ ๊ฒ ์ ํ๋ฆฌ์ผ์ด์
์ ํต์ฌ์ ์ธ ๊ธฐ๋ฅ์์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ถ๋ฆฌํด์ Aspect
๋ผ๋ ๋ชจ๋๋ก ๋ง๋ค์ด์ ๊ฐ๋ฐํ๋ ๋ฐฉ๋ฒ๋ก ์ Aspect Oriented Progreamming
AOP๋ผ๊ณ ๋ถ๋ฆ
๋๋ค.
ํ ๊ฐ์ง ์ ์ํ์ค์ ์, AOP๋ OOP์์ ๋ถ๋ฆฌ๋ ์๋ก์ด ๊ฐ๋ ์ ๊ฐ๋ฐ๋ก ์ด ์๋ OOP๋ฅผ ๋๋ ๋ณด์กฐ์ ์ธ ๊ธฐ์ ์ด๋ผ๊ณ ๋ณด์๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ฆ, AOP๋ Aspect
๋ฅผ ๋ถ๋ฆฌํจ์ผ๋ก์จ ํต์ฌ ๋ก์ง์ ๊ตฌํํ๋๋ฐ ๋ถ๋ด์ด ์๋๋ก ๋ํ ์ต๋ํ ๊ฐ์ฒด์งํฅ ๊ธฐ์ ์ ์ ์งํ๋๋ก ๋๋ ๊ฐ๋ฐ๋ก ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค.
ํ๋ก์๋ฅผ ์ด์ฉํ AOP
์ง๊ธ๊น์ง ์ ํฌ๊ฐ ํด์๋ ๋ฐฉ์์ ๋๋ค. ํ๋ก์๋ก ๋ง๋ค์ด์ DI๋ก ์ฐ๊ฒฐ๋ ๋น ์ฌ์ด์ ์ ์ฉํด ํ๊ฒ์ ๋ฉ์๋ ํธ์ถ ๊ณผ์ ์ ์ฐธ์ฌํด์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ ์ฃผ๋ ๊ฒ์ ๋งํฉ๋๋ค.
๋
๋ฆฝ์ ์ผ๋ก ๊ฐ๋ฐํ ๋ถ๊ฐ๊ธฐ๋ฅ ๋ชจ๋์ ๋ค์ํ ํ๊ฒ ์ค๋ธ์ ํธ์ ๋ฉ์๋์ ๋ค์ด๋ด๋ฏนํ๊ฒ ์ ์ฉํด์ฃผ๊ธฐ ์ํด ๊ฐ์ฅ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. ๋ฐ๋ผ์, ์คํ๋ง AOP๋
ํ๋ก์ ๋ฐฉ์์ AOP๋ผ๊ณ ํ ์ ์์ต๋๋ค.
๋ฐ์ดํธ์ฝ๋ ์์ฑ๊ณผ ์กฐ์์ ํตํ AOP
AOPํ๋ ์์ํฌ์ ๋ํ๊ฒฉ์ธ AspectJ
๋ ํ๋ก์๋ฅผ ์ฌ์ฉํ์ง ์๋ ๋ํ์ ์ธ AOP ๊ธฐ์ ์
๋๋ค.
AspectJ
๋ ํ๋ก์ ์ฒ๋ผ ๊ฐ์ ์ ์ธ ์ญํ ์ ํ๋ ๊ฒ์ด ์๋๋ผ ์ง์ ํ๊ฒ ์ค๋ธ์ ํธ์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ฃ์ด์ฃผ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ ๋ค๊ณ ํ๋๋ผ๋ ๋ถ๊ฐ๊ธฐ๋ฅ ์ฝ๋๋ฅผ ์ง์ ๋ฃ์์๋ ์์ผ๋,
์ปดํ์ผ๋ ํ๊ฒ์ ํด๋์ค ํ์ผ ์์ฒด๋ฅผ ์์ ํ๊ฑฐ๋ ํด๋์ค๊ฐ JVM์ ๋ก๋ฉ๋๋ ์์ ์ ๊ฐ๋ก์ฑ์ ๋ฐ์ดํธ์ฝ๋๋ฅผ ์กฐ์ํ๋ ๋ณต์กํ ๋ฐฉ๋ฒ์ ์ฌ์ฉํฉ๋๋ค.
์ด์ ๊ฐ์ด ๋ฒ๊ฑฐ๋ก์ด ์ด์ ๋ฅผ ํ๋ ์ด์ ๋
1. DI์ ๋์์ ๋ฐ์ง ์์๋ ๋ฉ๋๋ค.
๊ฑฐ์ ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ๋ด์์๋ ํ๋ก์๋ฅผ ํตํด์ ๊ฐ๋ฅํ์ง๋ง, ์ข ๋ ํน๋ณํ ์ํฉ์ด ํ์ํ ๊ฒฝ์ฐ์ ์ด์๊ฐ์ ๋ฐฉ๋ฒ์ ์ด๋ค๊ณ ํฉ๋๋ค.