AOP

    AOP 的概念通过搜索引擎一定是看烦了,而且看了也没什么大卵用,不贴近实际。

    我先举个 AOP 实际应用的简单例子,比如在写一个方法的时候,可能要针对某个方法写前置和后置操作,传统写法如下:

    运行结果:

    1. Parent->test()
    2. Child->__afterTest()

    这种写法你需要事先定义好前置和后置方法,如果需要前后置的方法一多,写起来会非常繁琐。

    AOP 可以很好地解决这个问题,不仅可以在编写上不用事先定义这么多方法,还非常有助于解耦。

    普通的类,你要切入的类。

    切入点 Pointcut

    普通类中的方法,你要切入的方法。

    连接点 Joinpoint

    在这个方法相关的什么时机触发通知,比如:调用的前置后置、抛出异常等。

    在连接点触发的通知,比如在前置操作触发,通知里写前置的具体实现。

    @Before

    前置操作

    @After

    后置操作

    @Around

    环绕操作。先触发环绕操作,在前置操作前和后置操作后,都可以做一些事情。甚至可以完全不让原方法运行,在环绕中实现该方法功能。

    @AfterReturning

    在原方法返回后触发,可以修改返回值

    @AfterThrowing

    在抛出异常后触发,允许设置allowdeny,设置允许和拒绝捕获的异常类

    使用注解注入方法

    监听池子的资源获取和释放:

    1. <?php
    2. namespace Test;
    3. use Imi\Aop\JoinPoint;
    4. use Imi\Aop\Annotation\After;
    5. use Imi\Aop\Annotation\Aspect;
    6. /**
    7. * @Aspect
    8. */
    9. class Pool
    10. {
    11. /**
    12. * @PointCut(
    13. * allow={
    14. * "Imi\*Pool*::getResource",
    15. * "Imi\*Pool*::release",
    16. * }
    17. * )
    18. * @After
    19. * @param JoinPoint $a
    20. * @return void
    21. */
    22. {
    23. echo $joinPoint->getType() . ' ' . get_parent_class($joinPoint->getTarget()) . '::' . $joinPoint->getMethod() . '(): ' . $joinPoint->getTarget()->getFree() . '/' . $joinPoint->getTarget()->getCount() . PHP_EOL;
    24. }
    25. }

    运行效果:

    1. after Imi\Redis\CoroutineRedisPool::getResource(): 0/1
    2. after Imi\Redis\CoroutineRedisPool::release(): 1/1

    类名、方法名和命名空间没有要求,只要beanScan里能扫描到即可。

    类注释中必须写@Aspect表名是一个切面类

    @After代表在该方法调用后触发

    注入带有注解的方法

    可参考imi\src\Db\Aop\TransactionAop.php文件:

    实现代码

    1. namespace Test;
    2. use Imi\Aop\JoinPoint;
    3. class Test
    4. {
    5. * @param JoinPoint $a
    6. * @return void
    7. */
    8. public function test(JoinPoint $joinPoint)
    9. {
    10. echo $joinPoint->getType() . ' ' . get_parent_class($joinPoint->getTarget()) . '::' . $joinPoint->getMethod() . '(): ' . $joinPoint->getTarget()->getFree() . '/' . $joinPoint->getTarget()->getCount() . PHP_EOL;
    11. }
    12. }

    对类没有任何要求,方法只需要参数对即可。

    配置

    1. <?php
    2. return [
    3. \Test\Test::class => [
    4. // 固定写法methods
    5. 'methods' => [
    6. // 方法名
    7. 'test' => [
    8. // 指定切入点
    9. 'pointCut' => [
    10. 'allow' => [
    11. "Imi\*Pool*::getResource",
    12. "Imi\*Pool*::release",
    13. ]
    14. ],
    15. 'after' => [
    16. ]
    17. ]
    18. ]
    19. ],
    20. ];
    <?php
    namespace Test;
    
    use Imi\Aop\JoinPoint;
    use Imi\Aop\AroundJoinPoint;
    use Imi\Aop\Annotation\After;
    use Imi\Aop\Annotation\Around;
    use Imi\Aop\Annotation\Aspect;
    use Imi\Aop\Annotation\Before;
    use Imi\Aop\Annotation\PointCut;
    use Imi\Aop\AfterThrowingJoinPoint;
    use Imi\Aop\AfterReturningJoinPoint;
    use Imi\Aop\Annotation\AfterThrowing;
    use Imi\Aop\Annotation\AfterReturning;
    
    /**
     * @Aspect
     */
    class Test
    {
        /**
         * 前置操作
         * @PointCut(
         *         allow={
         *             "ImiDemo\HttpDemo\MainServer\Model\Goods::getScore",
         *         }
         * )
         * @Before
         * @param JoinPoint $a
         * @return void
         */
        public function before(JoinPoint $joinPoint)
        {
            // 修改参数
            // $joinPoint->setArgs(/*参数数组*/);
            echo 'getScore()-before', PHP_EOL;
        }
    
        /**
         * 后置操作
         * @PointCut(
         *         allow={
         *             "ImiDemo\HttpDemo\MainServer\Model\Goods::getScore",
         *         }
         * )
         * @After
         * @param JoinPoint $a
         * @return void
         */
        public function after(JoinPoint $joinPoint)
        {
            echo 'getScore()-after', PHP_EOL;
        }
    
        /**
         * 环绕
         * @PointCut(
         *         allow={
         *             "ImiDemo\HttpDemo\MainServer\Model\Goods::getScore1",
         *         }
         * )
         * @Around
         * @return mixed
         */
        public function around(AroundJoinPoint $joinPoint)
        {
            var_dump('调用前');
            // 调用原方法,获取返回值
            $result = $joinPoint->proceed();
            var_dump('调用后');
            return 'value'; // 无视原方法调用后的返回值,强制返回一个其它值
            return $result; // 返回原方法返回值
        }
    
        /**
         * 返回值
         * @PointCut(
         *         allow={
         *             "ImiDemo\HttpDemo\MainServer\Model\Goods::getScore",
         *         }
         * )
         * @AfterReturning
         * @param AfterReturningJoinPoint $joinPoint
         * @return void
         */
        public function afterReturning(AfterReturningJoinPoint $joinPoint)
        {
            $joinPoint->setReturnValue('修改返回值');
        }
    
        /**
         * 异常捕获
         * @PointCut(
         *         allow={
         *             "ImiDemo\HttpDemo\MainServer\Model\Goods::getScore",
         *         }
         * )
         * @AfterThrowing
         * @param AfterThrowingJoinPoint $joinPoint
         * @return void
         */
        public function afterThrowing(AfterThrowingJoinPoint $joinPoint)
        {
            // 异常不会被继续抛出
            $joinPoint->cancelThrow();
            var_dump('异常捕获:' . $joinPoint->getThrowable()->getMessage());
        }
    }
    

    属性注入

    如下代码例子,定义一个类,使用@Inject注解来注释属性,在通过getBean()实例化时,会自动给被注释的属性赋值相应的实例对象。

    非 Bean 类使用属性注入

    imi 提供了一个 Imi\Bean\Traits\TAutoInject 来让非 Bean 类也能够使用属性注入。也就是直接new对象,也可以自动注入属性。当然,这个类的命名空间必须在beanScan中配置,能被扫描到才可以正常注入。

    无构造方法的类:

    namespace Test;
    
    use Imi\Aop\Annotation\Inject;
    
    class Test
    {
        use Imi\Bean\Traits\TAutoInject;
    
        /**
         * @Inject("XXX")
         */
        public $xxx;
    }
    
    $test = new Test;
    $test->xxx; // 会被自动注入,不用手动初始化
    

    有构造方法的类:

    namespace Test;
    
    use Imi\Aop\Annotation\Inject;
    
    class Test
    {
        use Imi\Bean\Traits\TAutoInject;
    
        /**
         * @Inject("XXX")
         */
        public $xxx;
    
        private $value;
    
        public function __construct()
        {
            $this->__autoInject(); // 手动调用 __autoInject() 方法
            $this->value = 123;
        }
    }
    
    $test = new Test;
    $test->xxx; // 会被自动注入,不用手动初始化
    
    
    

    可以直接注入值,也可以使用值注入注解。