应用面向方面的编程
1.引言
面向方面的編程的主要目標是將跨領域的關注點分離。 當我們談論跨領域的關注時,我們指的是在我們的系統或應用程序中的多個地方使用的通用功能。 這些概念包括:
- 記錄中
- 交易管理
- 錯誤處理
- 監控方式
- 安全
實現這種分離的方法是將這些概念模塊化。 這將使我們保持業務邏輯類整潔,僅包含設計該類的代碼。 如果我們不對這些問題進行模塊化,則將導致代碼糾結(該類包含不同的問題)和代碼分散(相同的問題將散布在整個系統中)。
在此示例中,我們有一個Spring MVC應用程序,該應用程序訪問所請求的數據(客戶和訂單)并顯示一個包含其信息的頁面。 我們可以看一下不同的層:
在上圖中,我們可以理解,功能分散在不同的類中(在每個服務中實現監視),并且某些類包含不同的關注點(例如,ClientController類包含日志記錄和異常處理)。 為了解決這個問題,我們將寫一些方面來實現我們的跨領域關注點。 目標是實現以下模型:
每個類僅包含與業務邏輯相關的代碼,而各方面將負責攔截代碼,以注入跨領域的關注點。
讓我們看一個例子。
- 源代碼可以在github上找到。
2.檢查控制器代碼
ClientController:
@Controller public class ClientController {@Autowiredprivate ClientService clientService;private static Logger mainLogger = LoggerFactory.getLogger("generic");private static Logger errorLogger = LoggerFactory.getLogger("errors");@RequestMapping("/getClients")public String getClients(Model model, @RequestParam("id") int id) {mainLogger.debug("Executing getClients request");try {Client client = clientService.getClient(id);model.addAttribute("client", client);} catch (DataAccessException e) {errorLogger.error("error in ClientController", e);NotificationUtils.sendNotification(e);return "errorPage";}return "showClient";} }該控制器的目的在于檢索客戶端并返回一個顯示其信息的視圖,但是,正如您所看到的,此代碼包含其他邏輯。 一方面,它處理服務可能引發的異常,并將其重定向到錯誤頁面。 另一方面,如果發生錯誤,它會生成日志記錄信息和通知發送。 所有這些代碼對于該應用程序中的所有控制器(可能還有其他類)都是通用的。
的確,我們本可以使用@ControllerAdvice批注來集中處理異常,但是本文的目標是了解如何使用Spring AOP完成它。
訂單控制器也會發生同樣的情況。 我不會在這里包括它,因為我不想讓帖子過長。 如果您想檢查一下,可以獲取上一個鏈接中包含的源代碼。
3.檢查服務代碼
客戶服務:
@Service("clientService") public class ClientServiceImpl implements ClientService {@Autowiredprivate ClientRepository clientRepository;private static Logger mainLogger = LoggerFactory.getLogger("generic");private static Logger monitorLogger = LoggerFactory.getLogger("monitoring");@Override@Transactional(readOnly = true)public Client getClient(int id) {mainLogger.debug("Accessing client service");long startTime = System.currentTimeMillis();Client client = clientRepository.getClient(id);long totalTime = System.currentTimeMillis() - startTime;monitorLogger.info("Invocation time {}ms ", totalTime);return client;} }除了服務調用之外,它還包含日志記錄的生成以及每個調用中執行時間的監視。
如果需要使用程序化事務管理,我們還可以使用方面來使事務管理模塊化,但是在本示例中并非如此。
4.數據訪問層
ClientRepositoryImpl:
@Repository public class ClientRepositoryImpl implements ClientRepository {private JdbcTemplate template;private RowMapper<Client> rowMapper = new ClientRowMapper();private static final String SEARCH = "select * from clients where clientId = ?";private static final String COLUMN_ID = "clientId";private static final String COLUMN_NAME = "name";public ClientRepositoryImpl() {}public ClientRepositoryImpl(DataSource dataSource) {this.template = new JdbcTemplate(dataSource);}public Client getClient(int id) {return template.queryForObject(SEARCH, rowMapper, id);}private class ClientRowMapper implements RowMapper<Client> {public Client mapRow(ResultSet rs, int i) throws SQLException {Client client = new Client();client.setClientId(rs.getInt(COLUMN_ID));client.setName(rs.getString(COLUMN_NAME));return client;}} }該代碼不包含任何橫切關注點,但我將其包括在內以顯示所有示例應用程序層。
5,激活AOP
要配置AOP,必須導入以下依賴項:
<dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>3.2.1.RELEASE</version> </dependency> <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.6.8</version> </dependency>在Spring配置文件中,我們需要添加以下標簽:
<context:component-scan base-package="xpadro.spring.mvc.aop"/> <aop:aspectj-autoproxy/>component-scan標簽將在基本包中搜索,以找到我們的方面。 要使用自動掃描,您不僅需要使用@Aspect注釋定義方面類,而且還需要包含@Component注釋。 如果不包括@Component,則需要在xml配置文件中定義方面。
6,集中錯誤處理
我們將使用@Around建議來編寫方面。 該建議將截獲所有使用@RequestMapping注釋進行注釋的方法,并將負責調用該方法,以捕獲服務引發的異常。
@Component @Aspect public class CentralExceptionHandler {private static Logger errorLogger = LoggerFactory.getLogger("errors");@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping) && target(controller)")public String handleException(ProceedingJoinPoint jp, Object controller) throws Throwable {String view = null;try {view = (String) jp.proceed();} catch (DataAccessException e) {errorLogger.error("error in {}", controller.getClass().getSimpleName(), e);NotificationUtils.sendNotification(e);return "errorPage";}return view;} }@Target批注允許我們引用被攔截的類。 現在我們有了方面處理的異常處理,因此我們可以在控制器中擺脫這種邏輯。
@Controller public class ClientController {@Autowiredprivate ClientService clientService;private static Logger mainLogger = LoggerFactory.getLogger("generic");//private static Logger errorLogger = LoggerFactory.getLogger("errors");@RequestMapping("/getClients")public String getClients(Model model, @RequestParam("id") int id) {mainLogger.debug("Executing getClients request");//try {Client client = clientService.getClient(id);model.addAttribute("client", client);//} catch (DataAccessException e) {//errorLogger.error("error in ClientController", e);//NotificationUtils.sendNotification(e);//return "errorPage";//}return "showClient";} }僅需注意,您可以通過以下建議攔截控制器拋出的異常:
@AfterThrowing(pointcut="@annotation(org.springframework.web.bind.annotation.RequestMapping)", throwing="e")但是請注意,此建議不會阻止異常的傳播。
7,集中日志
日志記錄方面有兩個建議,一個關于控制器日志,另一個關于服務日志:
@Aspect @Component public class CentralLoggingHandler {private static Logger mainLogger = LoggerFactory.getLogger("generic");@Before("@annotation(org.springframework.web.bind.annotation.RequestMapping) && @annotation(mapping)")public void logControllerAccess(RequestMapping mapping) {mainLogger.debug("Executing {} request", mapping.value()[0]);}@Before("execution(* xpadro.spring.mvc.*..*Service+.*(..)) && target(service)")public void logServiceAccess(Object service) {mainLogger.debug("Accessing {}", service.getClass().getSimpleName());} }8.最后,監控問題
我們將寫另一個方面來監視問題。 建議如下:
@Aspect @Component public class CentralMonitoringHandler {private static Logger monitorLogger = LoggerFactory.getLogger("monitoring");@Around("execution(* xpadro.spring.mvc.*..*Service+.*(..)) && target(service)")public Object logServiceAccess(ProceedingJoinPoint jp, Object service) throws Throwable {long startTime = System.currentTimeMillis();Object result = jp.proceed();long totalTime = System.currentTimeMillis() - startTime;monitorLogger.info("{}|Invocation time {}ms ", service.getClass().getSimpleName(), totalTime);return result;} }9,檢查最終代碼
在將所有交叉問題模塊化后,我們的控制器和服務僅包含業務邏輯:
@Controller public class ClientController {@Autowiredprivate ClientService clientService;@RequestMapping("/getClients")public String getClients(Model model, @RequestParam("id") int id) {Client client = clientService.getClient(id);model.addAttribute("client", client);return "showClient";} }@Service("clientService") public class ClientServiceImpl implements ClientService {@Autowiredprivate ClientRepository clientRepository;@Override@Transactional(readOnly = true)public Client getClient(int id) {return clientRepository.getClient(id);} }10,結論
我們已經看到了如何應用面向方面的編程來保持我們的代碼整潔,并專注于針對其設計的邏輯。 在使用AOP之前,只需考慮其已知限制。
翻譯自: https://www.javacodegeeks.com/2014/02/applying-aspect-oriented-programming.html
總結
- 上一篇: 部署到Maven中央存储库
- 下一篇: JDBC布尔兼容性列表