`
ry.china
  • 浏览: 139823 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类
最新评论

一个用Spring AOP实现异常处理和记录程序执行时间的实例

    博客分类:
  • java
阅读更多

原创   5.11 一个用Spring AOP实现异常处理和记录程序执行时间的实例 收藏

<script type="text/javascript"> document.body.oncopy = function() { if (window.clipboardData) { setTimeout(function() { var text = clipboardData.getData(&quot;text&quot;); if (text &amp;&amp; text.length&gt;300) { text = text + &quot;\r\n\n本文来自CSDN博客,转载请标明出处:&quot; + location.href; clipboardData.setData(&quot;text&quot;, text); } }, 100); } } </script><script type="text/javascript">function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&amp;u='+escape(d.location.href)+'&amp;c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>

5.11  一个用Spring AOP实现异常处理和记录程序执行时间的实例

虽然前面也给出了Spring AOP的一些实例,但因为主要目的是为了介绍Spring的知识点,不一定会很完整,下面笔者就通过一个完整的用Spring AOP实现异常处理和记录程序执行时间的实例来展示使用Spring AOP的整个过程。

5.11.1  异常处理和记录程序执行时间的实例简介

这个实例主要用于在一个系统的所有方法执行过程中出现异常时,把异常信息都记录下来,另外记录每个方法的执行时间。用两个业务逻辑来说明上述功能,这两个业务逻辑首先使用Spring AOP的自动代理功能,然后一个用Java的动态代理,一个用CGLIB代理。

实现思路是:仍然使用前面所建的Java工程 myApp,首先定义负责异常处理的Advice为ExceptionHandler.java,定义记录程序执行时间的Advice为 TimeHandler.java,接着定义业务逻辑接口LogicInterface.java,编写实现业务逻辑接口的类Logic1.java,该 业务逻辑在Spring AOP中使用Java的动态代理,编写另一个业务逻辑Logic2.java不实现业务逻辑接口,该业务逻辑在Spring AOP中使用CGLIB代理,然后使用自动代理定义配置文件config.xml,最后编写测试程序TestAop.java,执行它并查看输出结果。下 面就一步一步来实现这个实例。

5.11.2  定义负责异常处理的Advice为ExceptionHandler.java

在myApp工程的com.gc.action包中 新建ExceptionHandler.java,该类主要负责当程序执行过程中出现异常时,把异常信息都记录下来。而Spring提供的通知类型中 Throw通知可以实现这个功能,因此这里使用Throw通知类型来实现Advice,类ExceptionHandler必须实现 ThrowsAdvice接口,重写afterThrowing()方法。ThrowsAdvice.java的示例代码如下:

//******* ExceptionHandler.java**************

package com.gc.action;

import java.lang.reflect.Method;

import org.apache.log4j.Level;

import org.apache.log4j.Logger;

import org.springframework.aop.ThrowsAdvice;

//使用Throw通知类型来实现Advice

public class ExceptionHandler implements ThrowsAdvice{

         private Logger logger = Logger.getLogger(this.getClass().getName());

    //重写afterThrowing()方法

    public void afterThrowing(Method method, Object[ ] args, Object target, Throwable subclass) throws Throwable {

                   logger.log(Level.INFO, args[0] + " 执行 " + method.getName() + " 时有异常抛出...." + subclass);

   }

}

代码说明:

  ●       必须重写afterThrowing()方法。

  ●       当程序有异常发生时,就会输出“×××执行×××方法时有异常抛出”和具体异常的记录信息。

5.11.3  定义记录程序执行时间的Advice为TimeHandler.java

在myApp工程的com.gc.action包中 新建TimeHandler.java,该类主要负责记录每个方法的执行时间,而Spring提供的通知类型中Around通知可以实现这个功能,因此这 里使用Around通知类型来实现Advice,类TimeHandler必须实现MethodInterceptor接口,重写invoke()方法。 TimeHandler.java的示例代码如下:

//******* TimeHandler.java**************

package com.gc.action;

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

import org.apache.log4j.Level;

import org.apache.log4j.Logger;

//类TimeHandler必须实现MethodInterceptor接口

public class TimeHandler implements MethodInterceptor{

         private Logger logger = Logger.getLogger(this.getClass().getName());

    //重写invoke()方法

    public Object invoke(MethodInvocation methodInvocation) throws Throwable {

                   long procTime = System.currentTimeMillis();

                   logger.log(Level.INFO, methodInvocation.getArguments()[0] + " 开始执行 " + methodInvocation.getMethod() + " 方法"); 

        try {

          Object result = methodInvocation.proceed();

          return result;

        }

        finally {

                            //计算执行时间

procTime = System.currentTimeMillis() - procTime;

                            logger.log(Level.INFO, methodInvocation.getArguments()[0] + " 执行 " + methodInvocation.getMethod() + " 方法结束");

                            logger.log(Level.INFO, "执行 " + methodInvocation.getMethod().getName() + " 方法共用了 " + procTime + "毫秒");

        }

   }

}

代码说明:

  ●       必须重写invoke()方法。

  ●       当有方法执行时,就会输出“×××在什么时间开始执行×××方法”、“×××在什么时间执行×××方法结束”和“执行×××方法共用了×××毫秒”的记录信息。

5.11.4  定义业务逻辑接口LogicInterface.java

在myApp工程的com.gc.impl包中新建接口LogicInterface.java,该接口主要用来实现使用Spring AOP的动态代理机制,在这个接口里共定义了3个方法——新增、修改和删除。LogicInterface.java的示例代码如下:

//******* LogicInterface.java**************

package com.gc.impl;

//该接口主要用来实现使用Spring AOP的动态代理机制

public interface LogicInterface {

         public void doInsert(String name);

         public void doUpdate(String name);

         public void doDelete(String name);

}

5.11.5  编写实现业务逻辑接口的类Logic1.java

在myApp工程的com.gc.action包中 新建业务逻辑类Logic1.java,该类主要负责具体的业务逻辑,这个类实现了前面定义的接口LogicInterface,并重写了接口 LogicInterface定义的3个方法——新增、修改和删除,用来实现使用Spring AOP的动态代理机制,并在删除方法里增加一个“i = i / 0”,用来模拟异常的发生。Logic1.java的示例代码如下:

//******* Logic1.java**************

package com.gc.action;

import com.gc.impl.LogicInterface;

//实现这个接口

public class Logic1 implements LogicInterface{

         //负责新增

         public void doInsert(String name){

                   System.out.println("执行具体负责新增的业务逻辑…");

                   for (int i = 0; i < 100000000; i++) {

                            //模拟执行时间

                   }

         }

         //负责修改

         public void doUpdate(String name){

                   System.out.println("执行具体负责修改的业务逻辑…");

                   for (int i = 0; i < 200000000; i++) {

                            //模拟执行时间

                   }

         }

         //负责删除

         public void doDelete(String name){

                   System.out.println("执行具体负责删除的业务逻辑…");

                   for (int i = 0; i < 300000000; i++) {

                            i = i / 0;//模拟异常发生

                   }

         }

}

5.11.6  编写一个不实现业务逻辑接口的类Logic2.java

在myApp工程的com.gc.action包中 新建业务逻辑类Logic2.java,该类主要负责具体的业务逻辑——新增、修改和删除,这个类不实现前面定义的接口LogicInterface,用 来实现使用Spring AOP的CGLIB代理机制,并在删除方法里增加一个“i = i / 0”,用来模拟异常的发生。Logic2.java的示例代码如下:

//******* Logic2.java**************

package com.gc.action;

//该类采用CGLIB代理机制

public class Logic2 {

         //负责新增

         public void doInsert(String name){

                   System.out.println("执行具体负责新增的业务逻辑…");

                   for (int i = 0; i < 100000000; i++) {

                            //模拟执行时间

                   }

         }

//负责修改

         public void doUpdate(String name){

                   System.out.println("执行具体负责修改的业务逻辑…");

                   for (int i = 0; i < 200000000; i++) {

                            //模拟执行时间

                   }

         }

//负责删除

         public void doDelete(String name){

                   System.out.println("执行具体负责删除的业务逻辑…");

                   for (int i = 0; i < 300000000; i++) {

                            i = i / 0;//模拟异常发生

                   }

         }

}

5.11.7  使用自动代理定义配置文件config.xml

在myApp根目录下,新建一个Spring的配置文件exception_config.xml,主要用来使用Spring的自动代理功能,代理本系统所有的程序。exception_config.xml的示例代码如下:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

 "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

  <bean id="logic1" class="com.gc.action.Logic1"/>

  <bean id="logic2" class="com.gc.action.Logic2"/>

  <!--设定为自动代理-->

  <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

  <!--负责记录有异常发生时的信息-->

  <bean id="exceptionHandler" class="com.gc.action.ExceptionHandler"/>

 

  <bean id="exceptionHandlereAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">

        <property name="advice">

            <ref bean="exceptionHandler"/>

        </property>

                   <!--对指定类的任何方法有效-->

        <property name="patterns">

            <value>.*.*</value>

        </property>

  </bean>

  <!--负责记录方法的记录时间-->

  <bean id="timeHandler" class="com.gc.action.TimeHandler"/>

  <bean id="timeHandlerAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">

        <property name="advice">

            <ref bean="timeHandler"/>

        </property>

                   <!--对指定类的任何方法有效-->

        <property name="patterns">

            <value>.*.*</value>

        </property>

    </bean>

</beans>

5.11.8  编写测试类Logic1的程序TestAop.java

在myApp工程的com.gc.test包中新建测试类TestAop.java,该类主要负责对前面程序的测试和演示,这里先编写对业务逻辑Logic1.java的演示。TestAop.java的示例代码如下:

//******* TestAop.java**************

package com.gc.test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.gc.impl.LogicInterface;

public class TestAop {

         public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {

                   //通过ApplicationContext获取配置文档

                   ApplicationContext actx=new FileSystemXmlApplicationContext("exception_config.xml");

                   LogicInterface logic = (LogicInterface)actx.getBean("logic1");

                   //模拟执行新增、修改、删除方法

                   try {

                            logic.doInsert("张三");

                            logic.doUpdate("李四");

                            logic.doDelete("王五");

                   } catch (Exception ex) {

                   }

         }

}

5.11.9  输出自动代理时类Logic1异常处理和记录程序执行时间的信息

运行测试程序,即可看到记录Logic1.java中每个方法执行时间和在删除方法里捕获的异常的信息,如图5.16所示。

图5.16  记录Logic1.java中每个方法执行时间和在删除方法里捕获的异常信息

5.11.10  编写测试类Logic2的程序TestAop.java

上面主要是针对业务逻辑Logic1.java的测 试,所以在记录的信息中,类名显示的是Logic1实现的接口LogicInterface的名称,对于业务逻辑Logic2.java的测试,需要修改 测试代码TestAop.java。TestAop.java的示例代码如下:

//******* TestAop.java**************

package com.gc.test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.gc.action.Logic2;

public class TestAop {

         public static void main(String[ ] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {

//通过ApplicationContext获取配置文档

                   ApplicationContext actx=new FileSystemXmlApplicationContext("exception_config.xml");

                   Logic2 logic2 = (Logic2)actx.getBean("logic2");

                   //模拟执行新增、修改和删除方法

                   try {

                            logic2.doInsert("张三");

                            logic2.doUpdate("李四");

                            logic2.doDelete("王五");

                   } catch (Exception ex) {

 

                   }

         }

}

5.11.11  输出自动代理时类Logic2异常处理和记录程序执行时间的信息

运行测试程序,即可看到记录Logic2.java中每个方法执行时间和在删除方法里捕获的异常的信息,如图5.17所示。

图5.17  记录Logic2.java中每个方法执行时间和在删除方法里捕获的异常信息

上面主要是针对业务逻辑Logic2.java的测试,所以在记录的信息中,类名显示的是Logic2的名称。这是使用接口和不使用接口时显示信息的主要区别。

以上示例使用的是Spring AOP的自动代理,那对于使用Spring AOP的动态代理和CGLIB代理来说,应该怎么实现呢?下面就来讲解使用ProxyFactoryBean实现动态代理和CGLIB代理。

5.11.12  使用ProxyFactoryBean代理定义配置文件config.xml

改写Spring的配置文件 exception_config.xml,主要用来使用Spring的ProxyFactoryBean代理功能,对Logic1.java只记录程序 执行时间,不捕获发生的异常,对Logic2.java只捕获发生的异常,不记录程序执行时间,去掉前面的自动代理。 exception_config.xml的示例代码如下:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

 "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

  <bean id="logic1" class="com.gc.action.Logic1"/>

  <bean id="logic2" class="com.gc.action.Logic2"/>

  <!--设定为自动代理-->

  <!--负责记录有异常发生时的信息-->

  <bean id="exceptionHandler" class="com.gc.action.ExceptionHandler"/>

 

  <bean id="exceptionHandlereAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">

        <property name="advice">

            <ref bean="exceptionHandler"/>

        </property>

<!--对指定类的任何方法有效-->

        <property name="patterns">

            <value>.*.*</value>

        </property>

  </bean>

  <!--负责记录方法的记录时间-->

  <bean id="timeHandler" class="com.gc.action.TimeHandler"/>

  <bean id="timeHandlerAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">

        <property name="advice">

            <ref bean="timeHandler"/>

        </property>

        <!--对指定类的任何方法有效-->

        <property name="patterns">

            <value>.*.*</value>

        </property>

</bean>

<bean id="logic1Proxy" class="org.springframework.aop.framework.ProxyFactoryBean">

        <property name="proxyInterfaces">

            <value>com.gc.impl.LogicInterface</value>

        </property>

        <property name="target">

            <ref bean="logic1"/>

        </property>

<!--指定代理类-->

        <property name="interceptorNames">

            <list>

                <value>timeHandlerAdvisor</value>

            </list>

        </property>

    </bean>

    <bean id="logic2Proxy" class="org.springframework.aop.framework.ProxyFactoryBean">

        <property name="proxyTargetClass">

            <value>true</value>

        </property>

        <property name="target">

            <ref bean="logic2"/>

        </property>

<!--指定代理类-->

        <property name="interceptorNames">

            <list>

                <value>exceptionHandler</value>

            </list>

        </property>

    </bean>

</beans>

5.11.13  编写测试类Logic1的程序TestAop.java

这里先改写对业务逻辑Logic1.java的演示。TestAop.java的示例代码如下:

//******* TestAop.java**************

package com.gc.test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.gc.impl.LogicInterface;

public class TestAop {

         public static void main(String[ ] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {

//通过ApplicationContext获取配置文档

                   ApplicationContext actx=new FileSystemXmlApplicationContext("exception_config.xml");

                   LogicInterface logic = (LogicInterface)actx.getBean("logic1Proxy");

//模拟执行新增、修改和删除方法

                   try {

                            logic.doInsert("张三");

                            logic.doUpdate("李四");

                            logic.doDelete("王五");

                   } catch (Exception ex) {

 

                   }

         }

}

5.11.14  输出ProxyFactoryBean代理时类Logic1记录程序执行时间的信息

运行测试程序,即可看到记录Logic1.java中每个方法执行时间的信息,如图5.18所示。

图5.18  记录Logic1.java中每个方法执行时间的信息

5.11.15  编写测试类Logic2的程序TestAop.java

上面主要是针对业务逻辑Logic1.java的测 试,所以在记录的信息中,只显示了每个方法的执行时间,而没有捕获删除方法抛出的异常信息,对于业务逻辑Logic2.java的测试,使其只捕获删除方 法抛出的信息,而不记录每个方法执行的时间,需要修改测试代码TestAop.java。TestAop.java的示例代码如下:

//******* TestAop.java**************

package com.gc.test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.gc.action.Logic2;

public class TestAop {

         public static void main(String[ ] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {

//通过ApplicationContext获取配置文档

                   ApplicationContext actx=new FileSystemXmlApplicationContext("exception_config.xml");

                   Logic2 logic2 = (Logic2)actx.getBean("logic2Proxy ");

                   //模拟执行新增、修改和删除方法

                   logic2.doInsert("张三");

                   logic2.doUpdate("李四");

                   logic2.doDelete("王五");

         }

}

5.11.16  输出ProxyFactoryBean代理时类Logic2异常处理的信息

运行测试程序,即可看到记录Logic2.java中在删除方法里捕获的异常的信息,如图5.19所示。

图5.19  记录Logic2.java中在删除方法里捕获的异常信息

上面主要是针对业务逻辑Logic2.java的测试,所以在记录的信息中,只捕获删除方法抛出的异常信息,而不记录每个方法执行的时间。

 

5.12  小    结

本章首先从AOP的基本思想讲起,接着对Java的动态代理机制进行了讲解,然后讲解了AOP的3个重要概念,又使用不同的通知分别实现了日志输出的示例,最后通过一个完整的实例使读者更全面地了解了Spring中的AOP。

因为AOP在技术和思想上都还没有完全成熟,如果大量地应用AOP可能会有一定的负面作用,可能会达不到开发者的初衷,使得代码更难以理解。因此,要适度地使用AOP。

总的来说,AOP编程的实质就是:业务逻辑不再实现横切关注点,而是由单独的类来封装。当业务逻辑需要用到封装的横切关注点时,AOP会自动把封装的横切关注点插入到具体的业务逻辑中。

分享到:
评论

相关推荐

    简单spring aop 例子

    Spring AOP(面向切面编程)是Spring框架的重要组成部分,它提供了一种模块化和声明式的方式来处理系统中的交叉关注点问题,如日志、事务管理、安全性等。本示例将简要介绍如何在Spring应用中实现AOP,通过实际的...

    Spring AOP完整例子

    在Spring AOP的例子中,我们可能会创建一个`@RunWith(SpringJUnit4ClassRunner.class)`标记的测试类,以利用Spring的测试支持。在测试方法中,可以注入需要的bean,然后调用方法来触发AOP代理。这样,通知将在适当的...

    spring aop jar 包

    4. **连接点(Join Point)**:连接点是切点在实际运行时的实例,比如一个具体的方法调用。 5. **织入(Weaving)**:织入是将切面应用到目标对象并创建一个代理的过程。Spring AOP支持三种织入方式:编译时织入、...

    反射实现 AOP 动态代理模式(Spring AOP 的实现原理)

    例如,通过使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口,我们可以在运行时创建一个代理对象。代理对象在调用方法时会触发InvocationHandler的invoke()方法,从而可以在这个方法中实现...

    Spring AOP的简单实现

    Spring AOP(面向切面编程)是Spring框架中的一个重要组件,它允许我们在不修改源代码的情况下,对程序的行为进行统一管理和增强。在这个场景中,我们将使用Spring AOP来实现一个日志记录的功能,以追踪系统中各个...

    spring-aop实例

    Spring AOP(面向切面编程)是Spring框架的重要组成部分,它提供了一种强大的方式来实现横切关注点,如日志、事务管理、安全性等,从而解耦应用程序的核心业务逻辑。在Spring AOP中,关注点被模块化为独立的“切面”...

    Spring Aop使用实例

    - **连接点(Join Point)**:程序执行过程中特定的点,如方法调用、异常处理等。 - **通知(Advice)**:在特定连接点上执行的代码,比如在方法调用前后执行的代码。 - **切点(Pointcut)**:定义一组连接点的...

    springAop与spring定时器

    Spring AOP(面向切面编程)是Spring框架中的一个重要组件,它允许我们在不修改源代码的情况下,通过在程序运行时动态地将代码插入到方法调用中,来实现跨切面的关注点,如日志记录、性能监控、事务管理等。而Spring...

    Spring之AOP在鉴权和日志记录中的应用

    **Spring AOP在鉴权和日志记录中的应用** **一、引言** Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的一个重要特性,它为开发者提供了在不修改源代码的情况下,对应用程序进行功能增强...

    springAop的配置实现

    - **连接点(Join Point)**:程序执行中的某个特定点,如方法调用、异常处理等。 - **切入点(Pointcut)**:定义一组连接点的规则,表示哪些连接点将被通知执行。 - **代理(Proxy)**:Spring AOP通过代理模式来...

    JAVA 中Spring aop 实现日志记载

    在Java开发中,Spring AOP(面向切面编程)是一个强大的功能,用于实现日志记录。AOP允许我们在不修改原有代码的情况下,插入新的行为,比如日志记录,事务管理等。下面将详细介绍如何在Spring框架中使用AOP来实现...

    理解Spring AOP实现与思想 案例代码

    Spring AOP(面向切面编程)是Spring框架中的一个重要特性,它允许开发者在不修改源代码的情况下,通过插入额外的代码(称为切面)来增强应用程序的功能。这主要通过代理模式实现,使得我们可以集中处理系统中横切...

    反射实现 AOP 动态代理模式(Spring AOP 的实现 原理) - Java 例子 -

    Spring框架是Java中实现AOP的一个流行工具,它通过动态代理机制实现了这一功能。本文将深入探讨Spring AOP的实现原理,以及如何使用反射来实现动态代理模式。 首先,我们需要了解AOP的基本概念。AOP的核心思想是切...

    初探spring aop内部实现 java

    Spring AOP(面向切面编程)是Spring框架中的一个重要组件,它允许我们在不修改源代码的情况下,对程序的行为进行统一的管理和控制。本篇文章将深入探讨Spring AOP的内部实现,以及如何通过源代码理解其DataSource...

    spring AOP入门实例

    通过这个简单的日志记录实例,我们可以看到Spring AOP如何优雅地处理横切关注点,避免了代码的重复和侵入性。这只是一个基础的例子,实际上,Spring AOP支持更复杂的场景,如方法参数匹配、注解匹配等,可以满足更多...

    Spring 2.5 AOP 例子

    Spring 2.5 AOP(面向切面编程)是Java应用程序中的一个重要概念,它允许开发者在不修改原有代码的情况下插入新的行为或监控。这个例子旨在帮助我们理解和应用Spring框架的AOP特性。以下是对该主题的详细解释: 一...

    Spring AOP 的实现例子(基于XML配置实现)

    Spring AOP,即Spring的面向切面编程,是Spring框架中的一个重要组成部分,它提供了一种在不修改原有代码的情况下,对程序进行功能增强的技术。在实际开发中,我们经常使用AOP来处理如日志记录、事务管理、权限校验...

    springboot spring aop 拦截器注解方式实现脱敏

    在Spring Boot应用中,Spring AOP(面向切面编程)是一种强大的工具,它允许我们创建横切关注点,如日志记录、权限检查等,这些关注点可以被编织到应用程序的多个点上,而无需侵入核心业务逻辑。在本案例中,我们将...

    spring aop 经典例子(原创)

    Spring AOP,全称Aspect-Oriented Programming,是Spring框架中的一个重要组成部分,它引入了面向切面编程的概念,使得开发者可以将关注点分离,更好地实现业务逻辑与系统服务的解耦。在这个经典例子中,我们将深入...

Global site tag (gtag.js) - Google Analytics