Aspect-Oriented Programming (AOP)
AOP只是一種概念,因此許多人會覺得聽起來非常抽象,很難理解它的意思,原先我們使用OOP時,會發現我們都是由上到下的完成一個流程,但AOP卻會Cross-cutting concern,將整個流程橫切,如安全 (Security)檢查、交易(Transaction)等系統層面的服務(Service),在一些應用程式之中常被見到安插至各個物件的處理流程之 中,這些動作在AOP的術語中被稱之為Cross-cutting concerns。
由上一篇所說,Spring大部分建立在IOC(DI)技術上,並且讓Spring控管這些類別,也只有Spring所控管的類別才可以達到SpringAOP的效果,因此我們不免地要建立一個XML讓Spring可以映射這些類別,並且去管控。
若想看更詳細的AOP介紹可以來此AOP 觀念與術語,不過我這邊會先說明如何使用Spring AOP。
建立User.java
public class User {
private String account;
private String nickName;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
@Override
public String toString() {
return "User [account=" + account + ", nickName=" + nickName + "]";
}
}
建立UserDAO.java
@Component
public class UserDAO {
public User getUserById(int id) {
System.out.println("Method getUserById() called");
return new User();
}
public int setUser(User user) {
System.out.println("Method setUser() called");
return 0;
}
public int delUserById(int id) {
return 0;
}
}
建立UserAspect.java
@Aspect
public class UserAspect {
@Before("execution(* com.andy.userdashboard.dao.UserDAO.getUserById(..))")
public void logBeforeV1(JoinPoint joinPoint) {
System.out.println("UserAspect.logBeforeV1() : " + joinPoint.getSignature().getName());
}
@Before("execution(* com.andy.userdashboard.dao.UserDAO.*(..))")
public void logBeforeV2(JoinPoint joinPoint) {
System.out.println("UserAspect.logBeforeV2() : " + joinPoint.getSignature().getName());
}
@After("execution(* com.andy.userdashboard.dao.UserDAO.getUserById(..))")
public void logAfterV1(JoinPoint joinPoint) {
System.out.println("UserAspect.logAfterV1() : " + joinPoint.getSignature().getName());
}
@After("execution(* com.andy.userdashboard.dao.UserDAO.*(..))")
public void logAfterV2(JoinPoint joinPoint) {
System.out.println("UserAspect.logAfterV2() : " + joinPoint.getSignature().getName());
}
}
修改WebConfig.java
@Configuration
@EnableWebMvc
@EnableAspectJAutoProxy
@ComponentScan({"com.spring.example"})
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Bean
public UserAspect getUserAspect() {
return new UserAspect();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
建立HomeController.java
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@Autowired
private UserDAO userDAO;
/**
* Simply selects the home view to render by returning its name.
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
userDAO.getUserById(1);
userDAO.setUser(new User());
return "home";
}
}
當上述都完成後,只要呼叫首頁時候就會出現下面的回應
INFO : com.andy.userdashboard.controller.HomeController - Welcome home! The client locale is zh_TW.
UserAspect.logBeforeV1() : getUserById
UserAspect.logBeforeV2() : getUserById
Method getUserById() called
UserAspect.logAfterV2() : getUserById
UserAspect.logAfterV1() : getUserById
UserAspect.logBeforeV2() : setUser
Method setUser() called
UserAspect.logAfterV2() : setUser
小節
在這邊我們可以看到,當呼叫這個DAO某些方法時,會啟動我們所設置的AOP功能,目前Spring有提供幾種Advice,可以於Method呼叫前後或者其他橫切點使用,也可以取得所設定的物件以及回傳的物件,這種方式我相信很適合做日誌事件的使用,往後修改日誌事件時也不需修改主程式,在某些網站還有提到安全檢查提升的問題,我想這部分可以往後再去進行研究。