AOP(Aspect-Oriented Programming)是一种编程范式,旨在通过允许横切关注点的分离,提高模块化。它是一种面向对象编程的补充和完善,可以解决一些面向对象编程无法解决的问题。
在面向对象编程中,我们通常将问题拆分成一个个独立的对象,然后通过对象之间的交互来解决问题。然而,有时候我们需要在多个对象之间共享某些行为,例如日志记录、事务处理等。这些行为通常被称为横切关注点,因为它们跨越了多个对象,与业务逻辑无关,但却影响着业务逻辑的执行。
AOP允许我们将这些横切关注点从业务逻辑中分离出来,封装到独立的模块中,称为方面(Aspect)。方面可以定义在切入点和连接点上,切入点是指满足某些条件时执行方面代码的位置,连接点是指方面和目标代码之间的交互点。通过将横切关注点分离出来,我们可以更好地组织和管理代码,降低模块间的耦合度,提高代码的可重用性和可维护性。
在实现AOP时,通常需要使用专门的AOP框架。这些框架提供了切面编程的支持,包括切入点、连接点和方面等概念的实现。常见的AOP框架有Spring AOP、AspectJ等。
使用AOP可以带来很多好处。首先,它可以提高代码的可维护性和可重用性。通过将横切关注点分离出来,我们可以避免在多个地方重复编写相同的代码,同时也可以在不同的地方重用方面代码。其次,它可以降低模块间的耦合度。通过将横切关注点和业务逻辑分离出来,我们可以减少模块间的依赖关系,使模块更加独立和可替换。最后,它可以提高开发效率。通过将复杂的系统拆分成独立的方面,我们可以并行开发,提高开发效率和质量。
最近使用thinkphp 框架开发个项目,原框架没有提供日志记录功能,就着手使用php8 的注解功能 和AOP设计方法开发了日志记录功能。
下面简单介绍一下:
首先定义一个日志接口,这里定义了http请求处理后处理功能的接口方法:
namespace app;
/**
* Interface ReqAfterInter
* @package app
*/
interface ReqAfterInter
{
public function handler($request,$response);
}
编写一个日志记录功能类,继承该接口,实现handler类
#[\Attribute(\Attribute::TARGET_METHOD)]
class OperLog implements ReqAfterInter {
private $module = "";
private $method = "";
private $info = "";
/**
* OperLog constructor.
* @param string $method
* @param string $module
* @param string $info
* @function :构造函数
*/
public function __construct($method="",$module="",$info=""){
$this->module = $module;
$this->method = $method;
$this->info = $info;
}
/**
* @name:handler
* @datetime:2023/12/5
* @param $request
* @param $response
* @function:日志记录
*/
public function handler($request,$response)
{
$userId = $request->jwtPayload->userId;
$param = $request->param();
$path = $request->pathInfo();
$controller = $request->controller();
$action = $request->action();
try{
$data=[
'user_id' => $userId,
'module' => $module,
'method' => $method,
'info' => $info,
'param' => json_encode($param),
'path_info'=> $path,
'controller'=> $controller,
'action' => $action
];
Db::table("visit_log")->insert($data,true);
}catch (\Exception $e)
{
Log::error("userId:".$userId." 日志数据insert错误:".$e->getMessage());
}
}
接下来,就是在tp框架下 如何在request 请求后处理调用注解标记的方法了,这里使用了框架的中间件功能,我们添加一个后置请求的中间件,在
middleware目录下添加RequestAfterAspect.php 类,这个类负责动态加载OperLog 类,使用反射机制执行OperLog类。同时为了实现可以动态配置和加载OperLog 类, 我们增加配置文件 annotation.php 内容如下:
<?php
/**
* 返回需要使用的注解类
*/
return [
app\annotation\OperLog::class,
];
RequestAfterAspect.php 主要完成过滤请求方法,加载配置的注解类,调用注解类里的handler方法,内容如下:
<?php
namespace app\middleware;
use think\facade\App;
use think\facade\Config;
use think\facade\Log;
use think\facade\Route;
class RequestAfterAspect
{
private $annotations=[];
private $afterAnnotations=[];
private static $interfaceName = "ReqAfterInter";
/**
* RequestAfterAspect constructor.
*/
public function __construct() {
$dir = __DIR__;
$path = realpath($dir.'/../annotation.php');//获取配置文件路径
$this->annotations = include $path; // 加载注解类
foreach($this->annotations as $after)
{
$interfaces = class_implements($after);
foreach ($interfaces as $inters)
{
if(str_contains($inters,self::$interfaceName))
{
array_push($this->afterAnnotations,$after);
}
}
}
}
/**
* @name:handle
* @datetime:2023/11/30
* @param $request
* @param \Closure $next
* @return mixed
* @apiDescription
*/
public function handle($request, \Closure $next)
{
$response = $next($request);
if(empty($this->afterAnnotations))
{
return $response;
}
try{
$pathInfo = $request->pathInfo();
$action = $request->action();
//转换路由
$rule = Route::getRule($action);
if(!empty($rule)){
foreach ( $rule as $r)
{
//Log::info($r->getName());
$pathInfo = $r->getName();
}
}
if(!empty($rule)){
foreach ( $rule as $r)
{
$pathInfo = $r->getName();
}
}
Log::info("path_info:".$pathInfo);
$app = empty(Config::get('app.app_namespace'))?'app':Config::get('app.app_namespace');
$nsController = Config::get('route.controller_layer');
//处理多级控制器
$pathInfo = str_replace('.','/',$pathInfo);
$nss = explode('/',$pathInfo);
$n = count($nss);
if( $n > 1 )
{
$nameSpace = $app.'\\'.$nsController;
foreach ($nss as $item)
{
$nameSpace = $nameSpace."\\".$item;
}
$pos = strrpos($nameSpace, '\\');
if ($pos !== false) {
$methodName = substr_replace($nameSpace, "::", $pos, strlen('\\'));
$refMethod = new \ReflectionMethod($methodName);
foreach ($this->afterAnnotations as $an)
{
$attributes = $refMethod->getAttributes($an);
foreach ($attributes as $attr)
{
$anInstance = $attr->newInstance();
$anInstance->handler($request,$response);
}
}
}
}
} catch (\Exception $e)
{
Log::error("请求后处理错误:".$e->getMessage());
} finally {
return $response;
}
}
}