应用面向方面的编程

1.引言

面向方面的编程的主要目标是将跨领域的关注点分离。 当我们谈论跨领域的关注时,我们指的是在我们的系统或应用程序中的多个地方使用的通用功能。 这些概念包括:

  • 记录中
  • 交易管理
  • 错误处理
  • 监控方式
  • 安全

实现这种分离的方法是将这些概念模块化。 这将使我们保持业务逻辑类整洁,仅包含设计该类的代码。 如果我们不对这些问题进行模块化,则将导致代码纠结(该类包含不同的问题)和代码分散(相同的问题将散布在整个系统中)。

在此示例中,我们有一个Spring MVC应用程序,该应用程序访问所请求的数据(客户和订单)并显示一个包含其信息的页面。 我们可以看一下不同的层:

aop1

在上图中,我们可以理解,功能分散在不同的类中(在每个服务中实现监视),并且某些类包含不同的关注点(例如,ClientController类包含日志记录和异常处理)。 为了解决这个问题,我们将写一些方面来实现我们的跨领域关注点。 目标是实现以下模型:

aop2

每个类仅包含与业务逻辑相关的代码,而各方面将负责拦截代码,以注入跨领域的关注点。

让我们看一个例子。

  • 源代码可以在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之前,只需考虑其已知限制。

参考:在XavierPadró的Blog博客上,从我们的JCG合作伙伴 Xavier Padro 应用面向方面的编程 。

翻译自: https://www.javacodegeeks.com/2014/02/applying-aspect-oriented-programming.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/365249.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最大连续子序列----DP动态规划

1196: 最大连续子序列 时间限制: 1 Sec 内存限制: 128 MB提交: 44 解决: 20[提交][状态][讨论版] 题目描述 给定 K 个整数的序列{ N1, N2, ..., NK } &#xff0c;其任意连续子序列可表示为{ Ni, Ni1,...,Nj} &#xff0c;其中1 < i< j < K。最大连续子序列是所有…

linux钟java运行命令,在java中运行linux命令

我想在java中运行“ls”命令&#xff0c;我的代码是- 注意&#xff1a; - 我正在使用WINDOWS。在java中运行linux命令import java.io.IOException;public class Example{public void fn(){Runtime run Runtime.getRuntime();Process p null;String cmd "ls";try {…

python3seek_Python seek()和tell()函数详解

在讲解 seek() 函数和 tell() 函数之前&#xff0c;首先来了解一下什么是文件指针。我们知道&#xff0c;使用 open() 函数打开文件并读取文件中的内容时&#xff0c;总是会从文件的第一个字符(字节)开始读起。那么&#xff0c;有没有办法可以自定指定读取的起始位置呢&#xf…

n=n+1 放在print(s)的前/后的影响

# 123456.....100 ?#关键在于,当n为时,才print(s)n 1s 0while n < 101: s s n if n 100: #关键在于这一步, 我用if来判断,当n为100时,才print(s), 否则继续while循环 print(s) n n 1          转载于:https://www.cnblogs.co…

ScrollReveal.js – 帮助你实现超炫的元素运动效果

ScrollReveal.js 用于创建和管理元素进入可视区域时的动画效果&#xff0c;帮助你的网站增加吸引力。只需要给元素增加 data-scrollreveal 属性&#xff0c;当元素进入可视区域的时候会自动被触发设置好的动画。 您可能感兴趣的相关文章2013年最受欢迎的10篇前端开发博文小伙伴…

JDBC布尔兼容性列表

有趣的是&#xff0c;布尔类型只是在SQL标准后期才引入&#xff0c;即SQL&#xff1a;1999 。 即使在今天&#xff0c;并非所有数据库本身都支持BOOLEAN或BIT类型。 最重要的是&#xff0c;我们仍然可以在Oracle中等待一段时间。 这是2002年以来关于该主题的“问汤姆”的观点&a…

linux用户操作的日志,linux 用户操作记录并录入日志

1. 添加bash全局配置文件&#xff1a;cd /etc/profile.dvi log_command.sh输入如下内容&#xff1a;export PROMPT_COMMAND‘RETRN_VAL$?;logger -p local6.debug "$(whoami) [$$]: $(history 1 | sed "s/^[ ]*[0-9]\[ ]*//" ) [$RETRN_VAL]"‘2. 添加rsy…

我实在不懂Python的Asyncio

原语 事件循环(Event Loop)Awaitables和CoroutinesCoroutine WrappersAwaitables and FuturesTasksHandlesExecutorsTransport and Protocols如何使用Asyncio上下文数据个人想法这是Flask&#xff0c;Sentry的作者Armin Ronacher的一篇博客&#xff0c;这篇文章的影响很大&…

GDI+与WPF中的颜色简析

GDI与WPF中的颜色简析 原文:GDI与WPF中的颜色简析--------------------------------------------------------------------------------引用或转载时请保留以下信息&#xff1a;大可山 [MSN:a3news(AT)hotmail.com] http://www.zpxp.com http://www.brawdraw.com萝卜鼠在线图形…

pythondatetime_Date

在JavaScript中&#xff0c;Date对象用来表示日期和时间。要获取系统当前时间&#xff0c;用&#xff1a;var now new Date();now; // Wed Jun 24 2015 19:49:22 GMT0800 (CST)now.getFullYear(); // 2015, 年份now.getMonth(); // 5, 月份&#xff0c;注意月份范围是0~11&…

Panorama Viewer – jQuery 360度全景展示插件

jQuery Panorama Viewer 这款插件可以帮助你在网站中嵌入全景图片。要做到这一点&#xff0c;首先只需要在页面中引入最新的 jQuery 库&#xff0c;以及 jquery.panorama_viewer.js 和 panorama_viewer.css 到页面中&#xff0c;然后给图片添加 CSS 类“panorama”。现代浏览器…

优化您的ApplicationContext

Spring有一个问题&#xff0c;已经存在了一段时间&#xff0c;我在许多项目中都遇到过。 与Spring或Spring的Guys无关&#xff0c;这取决于像您和我这样的Spring用户。 让我解释一下……在Spring 2的过去&#xff0c;您必须手动配置Application Context&#xff0c;手动创建一个…

linux多线程编程(中嵌教育-嵌入式linux开发课件),linux多线程编程(中嵌教育-嵌入式linux开发课件).ppt...

linux多线程编程(中嵌教育-嵌入式linux开发课件).pptlinux多线程编程 Linux下线程概述 linux线程实现 1、Linux下线程概述 进程是系统中程序执行和资源分配的基本单位。每个进程有自己的数据段、代码段和堆栈段。 线程通常叫做轻型的进程。线程是在共享内存空间中并发执行的多道…

oracle group by 多类别_python数据关系型图表散点图系列多数据系列

多数据系列多数据系列的散点图需要使用不同的填充颜色(fill)和数据点形状(shape)这两个视觉特征来表示数据系列&#xff1b;绘制多数据系列散点图多数据系列散点图就是在单数据系列上添加新的数据系列&#xff1b;使用不同的填充颜色或形状区分数据系列&#xff1b;plotnine绘制…

图片和视频压缩例子

/*** 视频压缩 需要jave jar 包。到网上下载即可 * * param source 需要压缩的视频* param targetPath 压缩的目标路径* return*/public static boolean compressVideo(File source, String targetPath) {System.out.println("source:" source);System.out.println…

Web 开发中应用 HTML5 技术的10个实例教程

HTML5 作为下一代网站开发技术&#xff0c;无论你是一个 Web 开发人员或者想探索新的平台的游戏开发者&#xff0c;都值得去研究。借助尖端功能&#xff0c;技术和 API&#xff0c;HTML5 允许你创建响应性、创新性、互动性以及令人惊叹的漂亮网站。更进一步&#xff0c;你也可以…

linux ptrace 内核源码分析,linux 3.5.4 ptrace源码分析分析(系列一)

ptrace是linux系统中为了调试专门设立的一种系统调用。要想调试调试一个进程&#xff0c;有两种方式&#xff1a;PTRACE_TRACEME和PTRACE_ATTACH。这两种方式的主要区别可以概括为&#xff1a;PTRACE_TRACEME是子进程主动申请被TRACE。而PTRACE_ATTACH是父进程自己要attach到子…

在单元测试中访问私有字段

首先&#xff0c;让我大声说一下&#xff0c;您需要将代码设计为可测试的&#xff0c;以便通过公共方法测试私有字段。 但是&#xff0c;&#xff08;“ buts”是人们仍在编程而不是计算机本身的原因&#xff0c;所以在这里很高兴&#xff09;有时您想要并且应该更改一些私有字…

【LeetCode题解】160_相交链表

目录 160_相交链表描述解法一&#xff1a;哈希表思路Java 实现Python 实现解法二&#xff1a;双指针&#xff08;推荐&#xff09;思路Java 实现Python 实现160_相交链表 描述 编写一个程序&#xff0c;找到两个单链表相交的起始节点。 例如&#xff0c;下面的两个链表&#xf…

maya崩溃自动保存路径_maya 使用swig将插件编译成pyd,无缝使用内置数据实现加速计算模块...

前言&#xff1a;原本目的是想寻求一种方式来对cpu计算密集型代码部分进行加速替代&#xff0c;但是maya中mll插件的插件套路在传递参数上会占用大量的io&#xff0c;对于数据比较大的部分也会有相当消耗。如果全部写在c部分又感觉缺乏灵活性&#xff0c;所以琢磨的一种可以在p…