2021-11-06

AOP概述

Aspect Orieted Programming面向切面(方面)编程。

是一种编程模式,将分布在多个类中的功能封装到一个类中,这些功能称为cross-cutting concerns (横切关注点),如日志,事务,缓存,安全等等。

不是替代OOP,而是对其的补充。

软件纵向与横向结构

AOP的优点

减少了大量的代码重复。

功能组件之间的解耦。

常见AOP框架

        AspectJ、Spring、Aspectwerkz、JBoss AOP...

AOP的类型

静态AOP

修改应用程序的字节码,必要时会改变与扩展应用程序代码。

性能好,但是改动了切面需要重新编译整个应用程序。

AspectJ提供了实现。

动态AOP

实现过程是在运行时动态执行的。

性能稍差,但改动了切面无需重新编译整个应用程序。

Spring使用代理机制实现。

AOP核心概念

概念

说明

Advice,通知

需要单独封装的功能,定义在类的方法中。

JoinPoint,连接点

可以使用通知的地方。

PointCut,切入点

定义使用通知的连接点集合。

Aspect,切面

通知和切入点的组合。

Advisor,切面

通知和切入点的组合。

Weaving,织入

把切面应用到应用程序中的过程。

Target,目标

应用切面的对象。

Introduction,引入

向现有的类添加新方法或新属性。

Spring AOP

实现机制

代理Bean使用ProxyFactory产生。

内部根据情况使用不同的代理实现。

代理Bean注入到调用者中供使用。

两种代理实现

连接点类型

Spring只支持一种连接点类型:方法调用。

可以继承AspectJ提供更多类型的连接点支持。

通知类型

通知类型

说明

Before通知

方法调用之前执行。

可以访问调用方法的目标,方法及参数,但不能控制方法本身的执行。

如果通知抛出异常,通知链后续的通知及方法本身都不会执行。

Around通知

在方法调用之前和之后执行。

可以改变方法的执行逻辑。

After通知

AfterReturning通知

方法调用且返回结果后执行。

可以访问调用方法的目标,方法,参数及返回值,但不能控制方法本身的执行。

如果目标方法抛出异常,通知不会执行。

Throws通知

方法调用返回后执行,但是方法要抛出异常才会执行,一般用于处理特定的异常。

必须实现特定的方法(不是接口声明的)afterThrowing(...)

编程方式实现AOP

编写通知类,该类实现通知对应的接口。

使用ProxyFactory提供的方法设置目标及通知对象。

使用ProxyFactory获取目标的代理。

ProxyFactory类

构造方法

new ProxyFactory()

new ProxyFactory(Object target)

new ProxyFactory(Class<?>... proxyInterfaces)

new ProxyFactory(Class<?> proxyInterfaces, Interceptor interceptor)

new ProxyFactory(Class<?> proxyInterfaces, TargetSource targetSource)

普通方法

addAdvice()/removeAdvice()

增加/移除Advice到/从通知链。

setTarget()/setTargetClass()

设置目标对象。

addInterface()/setInterfaces()

增加/设置目标类的接口。

getProxy()

创建并返回代理对象。

adviceIncluded()

判断Advice是否存在于通知链中。

addAdvisor()/addAdvisors/removeAdvisor()

增加/移除Advisor到/从通知链。

通知对应的接口

Before

org.springframework.aop.MethodBeforeAdvice

AfterReturing

org.springframework.aop.AfterReturningAdvice

After

org.springframework.aop.AfterAdvice

Around

org.aopalliance.intercept.MethodInteceptor

Throws

org.springframework.aop.ThrowsAdvice

Introduction

org.springframework.aop.IntroductionInterceptor

before通知

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyBeforeAdvice myBeforeAdvice = new MyBeforeAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvice(myBeforeAdvice);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method();
    }
}
class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MyBeforeAdvice...");
    }
}
class Target {
    public void method() {
        System.out.println("target method...执行...");
    }
}

MyBeforeAdvice...
target method...执行...

afternReturning通知

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyAfterReturningAdvice myAfterReturningAdvice = new MyAfterReturningAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvice(myAfterReturningAdvice);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method();
    }
}
class MyAfterReturningAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("MyAfterReturningAdvice...执行结果 " + o);
    }
}
class Target {
    public String method() {
        System.out.println("target method1...执行...");
        return "myAfterReturningAdvice执行结果...";
    }
}

target method1...执行...
MyAfterReturningAdvice...执行结果 myAfterReturningAdvice执行结果...
 

Around通知

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyAroundAdvice myAroundAdvice = new MyAroundAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvice(myAroundAdvice);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method();
    }
}
class MyAroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("MyAroundAdvice start...");
        Object result = methodInvocation.proceed();
        System.out.println("方法执行结束..." + result);
        System.out.println("MyAroundAdvice end...");
        return null;
    }
}
class Target {
    public String method() {
        System.out.println("target method...执行...");
        return "MyAroundAdvice执行结果...";
    }
}

MyAroundAdvice start...
target method...执行...
方法执行结束...MyAroundAdvice执行结果...
MyAroundAdvice end...

throws通知

throwsAdvice方法调用返回后执行,但是方法要抛出异常才会执行,一般用于处理特定的异常。

必须实现特定的方法(不是接口声明的)。

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyThrowsAdvice myThrowsAdvice = new MyThrowsAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvice(myThrowsAdvice);
        Target proxy = (Target)proxyFactory.getProxy();
        try {
            proxy.method();
        } catch (Exception e) {
            //e.printStackTrace();
        }
    }
}
class Target {
    public void method() throws Exception{
        System.out.println("target method...执行...");
        throw new Exception("method error...");
    }
}

        注意:实现ThrowsAdvice接口的类必须是public修饰

public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
        System.out.println("MyThrowsAdvice异常: " + ex.getMessage());
    }
}

target method...执行...
MyThrowsAdvice异常: method error...

增加/设置目标类接口

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyBeforeAdvice myBeforeAdvice = new MyBeforeAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        //proxyFactory.setInterfaces(ITarget.class);
        proxyFactory.addAdvice(myBeforeAdvice);
        //ITarget proxy = (ITarget)proxyFactory.getProxy();
        Target proxy = (Target) proxyFactory.getProxy();
        System.out.println(proxy);
        proxy.method();
    }
}
class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MyBeforeAdvice... ");
    }
}
class Target implements ITarget{
    public void method(){
        System.out.println("before");
    }
}
interface ITarget{
    void method();
}

MyBeforeAdvice... 
org.crazy.aop.Target@6b71769e
MyBeforeAdvice... 
before

Pointcut接口

        使用advice通知时,在方法调用时都会使用相关定义的通知,某些非必要的方法就不需要通知如toString(),此时就需要切入点,指定具体哪些方法需要通知。

        该接口表示切入点

ClassFilter getClassFileter()

ClassFilter接口,用于匹配切入点或引入的目标类。

MethodMatcher getMethodMatcher()

MethodMatcher接口:用于匹配方法是否是合适的切入点以便应用切面。

        MethodMatcher接口的实现支持静态与动态两种类型,取决于首先调用法isRuntime()的返回值。

返回true

动态,先调用一次matches(Method m, Class<?> targetClass)方法,若该方法返回true,则后续调用matches(Method m, Class<?> targetClass, Object[] args)方法。

返回false

静态,只调用一次matches(Method m, Class<?> targetClass)方法。

        检查切入点时,首先调用getClassFileter()方法检查目标类是否匹配,若返回true,再调用getMethodMatcher()检查方法是否匹配。

        Spring提供了若干Pointcut的实现类

 

StaticMethodMatcherPointcut

实现静态MethodMatcher的抽象类。

重写其matches(Method method, @Nullable Class<?> targetClass)实现逻辑。

JdkRegexpMethodPointcut

使用Jdk的正则表达式匹配。

NameMatchMethodPointcut

使用方法名的简单匹配。

DynamicMethodMatchetPointcut

实现动态MethodMatcher的抽象类。

重写其matches(Method method, Class<?> targetClass, Object... args)实现逻辑。

AnnotationMatchingPointcut

用于匹配特定注解修饰的类或方法。

AspectJExpressionPointcut

使用AspectJ表达式作为切入规则;

需要AspectJ支持。

ComposablePointcut

用于操作多个Pointcut。

ControlFlowPointcut

切面应用于从指定类中调用的方法。

StaticMethodMatcherPointcut

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyBeforeAdvice myBeforeAdvice = new MyBeforeAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        Pointcut pointcut = new StaticMethodMatcherPointcut() {
            @Override
            public boolean matches(Method method, Class<?> aClass) {
                return "method2".equals(method.getName());
            }
        };
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,myBeforeAdvice);
        proxyFactory.setTarget(target);
        proxyFactory.addAdvisor(advisor);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method1();
        proxy.method2();
    }
}
class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MyBeforeAdvice... ");
    }
}
class Target {
    public void method1(){
        System.out.println("Target method1() running...");
    }
    public void method2(){
        System.out.println("Target method2() running...");
    }
}

Target method1() running...
MyBeforeAdvice... 
Target method2() running...

DynamicMethodMatchetPointcut

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyBeforeAdvice myBeforeAdvice = new MyBeforeAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        Pointcut pointcut = new DynamicMethodMatcherPointcut() {
            @Override
            public boolean matches(Method method, Class<?> aClass, Object... objects) {
                return "method1".equals(method.getName()) && objects.length > 0 && objects[0].equals("one");
            }
        };
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,myBeforeAdvice);
        proxyFactory.setTarget(target);
        proxyFactory.addAdvisor(advisor);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method1();
        proxy.method1("one");
        proxy.method2();
    }
}
class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MyBeforeAdvice... ");
    }
}
class Target {
    public void method1(){
        System.out.println("Target method1() running...");
    }
    public void method1(String str){
        System.out.println("Target method1(String) running...");
    }
    public void method2(){
        System.out.println("Target method2() running...");
    }
}

Target method1() running...
MyBeforeAdvice... 
Target method1(String) running...
Target method2() running...

JdkRegexpMethodPointcut

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyBeforeAdvice myBeforeAdvice = new MyBeforeAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
        pointcut.setPattern(".*method1.*");
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,myBeforeAdvice);
        proxyFactory.setTarget(target);
        proxyFactory.addAdvisor(advisor);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method1();
        proxy.method1("one");
        proxy.method2();
    }
}
class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MyBeforeAdvice... ");
    }
}
class Target {
    public void method1(){
        System.out.println("Target method1() running...");
    }
    public void method1(String str){
        System.out.println("Target method1(String) running...");
    }
    public void method2(){
        System.out.println("Target method2() running...");
    }
}

MyBeforeAdvice... 
Target method1() running...
MyBeforeAdvice... 
Target method1(String) running...
Target method2() running...

NameMatchMethodPointcut 

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyBeforeAdvice myBeforeAdvice = new MyBeforeAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.addMethodName("method1");
        pointcut.setMappedName("method2");
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,myBeforeAdvice);
        proxyFactory.setTarget(target);
        proxyFactory.addAdvisor(advisor);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method1();
        proxy.method1("one");
        proxy.method2();
    }
}
class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MyBeforeAdvice... ");
    }
}
class Target {
    public void method1(){
        System.out.println("Target method1() running...");
    }
    public void method1(String str){
        System.out.println("Target method1(String) running...");
    }
    public void method2(){
        System.out.println("Target method2() running...");
    }
}

Target method1() running...
Target method1(String) running...
MyBeforeAdvice... 
Target method2() running...

AnnotationMatchingPointcut 

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyBeforeAdvice myBeforeAdvice = new MyBeforeAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        //AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(MyAnnotation.class);
        AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MyAnnotation.class);
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,myBeforeAdvice);
        proxyFactory.setTarget(target);
        proxyFactory.addAdvisor(advisor);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method1();
        proxy.method1("one");
        proxy.method2();
    }
}
class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MyBeforeAdvice... ");
    }
}
@MyAnnotation
class Target {
    public void method1(){
        System.out.println("Target method1() running...");
    }
    public void method1(String str){
        System.out.println("Target method1(String) running...");
    }
    public void method2(){
        System.out.println("Target method2() running...");
    }
}
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({ElementType.TYPE,ElementType.METHOD})
@interface MyAnnotation {
}

Target method1() running...
Target method1(String) running...
Target method2() running...

AspectJExpressionPointcut

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyBeforeAdvice myBeforeAdvice = new MyBeforeAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* method*(..))");
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,myBeforeAdvice);
        proxyFactory.setTarget(target);
        proxyFactory.addAdvisor(advisor);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method1();
        proxy.method1("one");
        proxy.method2();
    }
}
class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MyBeforeAdvice... ");
    }
}
class Target {
    public void method1(){
        System.out.println("Target method1() running...");
    }
    public void method1(String str){
        System.out.println("Target method1(String) running...");
    }
    public void method2(){
        System.out.println("Target method2() running...");
    }
}

MyBeforeAdvice... 
Target method1() running...
MyBeforeAdvice... 
Target method1(String) running...
MyBeforeAdvice... 
Target method2() running...

ComposablePointcut 

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyBeforeAdvice myBeforeAdvice = new MyBeforeAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* method1*(..))");
        AnnotationMatchingPointcut pointcut1 = AnnotationMatchingPointcut.forMethodAnnotation(MyAnnotation.class);
        ComposablePointcut pointcut2 = new ComposablePointcut((Pointcut)pointcut);
        pointcut2.intersection(pointcut1);
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,myBeforeAdvice);
        proxyFactory.setTarget(target);
        proxyFactory.addAdvisor(advisor);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method1();
        proxy.method1("one");
        proxy.method2();
    }
}
class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MyBeforeAdvice... ");
    }
}
@MyAnnotation
class Target {
    public void method1(){
        System.out.println("Target method1() running...");
    }
    @MyAnnotation
    public void method1(String str){
        System.out.println("Target method1(String) running...");
    }
    public void method2(){
        System.out.println("Target method2() running...");
    }
}
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({ElementType.TYPE,ElementType.METHOD})
@interface MyAnnotation {
}

MyBeforeAdvice... 
Target method1() running...
MyBeforeAdvice... 
Target method1(String) running...
Target method2() running...

ControlFlowPointcut 

public class MyAdvice {
    public static void main(String[] args) {
        Target target = new Target();
        MyBeforeAdvice myBeforeAdvice = new MyBeforeAdvice();
        ProxyFactory proxyFactory = new ProxyFactory();
        ControlFlowPointcut pointcut = new ControlFlowPointcut(Service.class, "doService");
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,myBeforeAdvice);
        proxyFactory.setTarget(target);
        proxyFactory.addAdvisor(advisor);
        Target proxy = (Target)proxyFactory.getProxy();
        proxy.method1();
        proxy.method1("one");
        proxy.method2();
        new Service().doService(proxy);
    }
}
class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MyBeforeAdvice... ");
    }
}
class Target {
    public void method1(){
        System.out.println("Target method1() running...");
    }
    public void method1(String str){
        System.out.println("Target method1(String) running...");
    }
    public void method2(){
        System.out.println("Target method2() running...");
    }
}
class Service{
    public void doService(Target target) {
        target.method1();
    }
}

Target method1() running...
Target method1(String) running...
Target method2() running...
MyBeforeAdvice... 
Target method1() running...

Advisor接口

该接口表示切面,有两个子接口:

PointcutAdvisor

DefaultPointcutAdvisor是PointcutAdvisor的默认实现。

NameMatchMethodPointcutAdvisor

RegexpMethodPointcutAdvisor

AspectJPointcutAdvisor

IntroductionAdvisor

Pointcus类:

该类提供了两个常量及若干工具方法。

常量

SETTERS: 匹配set方法的Pointcut

GETTERS: 匹配get方法的Pointcut

方法

Pointcut union(Pointcut pc1, Pointcut pc2)

Pointcut intersection(Pointcut pc1, Pointcut pc2)

Introduction引入:

Introduction,可以在不改变目标对象的情况下动态引入新功能到已存在的对象上。

新功能使用实现了某个接口的类进行封装。

实际上是一个特殊类型的Around(环绕)通知。

只用于类级别上。

引入的实现方式:

声明接口

定义需要引入实现的方法;

声明引入

实现自定义的接口及IntroductionInterceptor接口(通常继承该接口的实现类DelegatingIntroductionInterceptor);

使用引入

直接将其作为Advice增加到ProxyFactory中。

实现IntroductionAdvisor接口,通常继承实现类DefaultIntroductionAdvosor,将引入作为参数传递给其构造方法,同时提供可选的IntroductionInfo限定类型,然后将其作为Advisor增加到ProxyFactory中。

实现方式一:

public class IntroductionTest {
    public static void main(String[] args) {
        Watch watch = new Watch();
        MyCallWatch myCallWatch = new MyCallWatch();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(watch);
        factory.addAdvice(myCallWatch);
        factory.setOptimize(true);//
        Watch w1 =(Watch)factory.getProxy();
        w1.showTime();
        CallWatch w2 = (CallWatch)w1;//将代理对象转换成合适的接口类型
        w2.call();
    }
}
class Watch {
    public void showTime() {
        System.out.println("My watch can showTime...");
    }
}
interface CallWatch {
    void call();
}
class MyCallWatch extends DelegatingIntroductionInterceptor implements CallWatch {
    @Override
    public void call() {
        System.out.println("My watch can call phone...");
    }
}

My watch can showTime...
My watch can call phone...

实现方式二:

public static void main(String[] args) {
        Watch watch = new Watch();
        MyCallWatch myCallWatch = new MyCallWatch();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(watch);
        factory.addAdvisor(new MyNewFunctionAdvisor(myCallWatch));
        factory.setOptimize(true);//
        Watch w1 =(Watch)factory.getProxy();
        w1.showTime();
        CallWatch w2 = (CallWatch)w1;//将代理对象转换成合适的接口类型
        w2.call();
        RecordSport w3 = (RecordSport)w1;
        w3.recordSport();
    }
}
class Watch {
    public void showTime() {
        System.out.println("My watch can showTime...");
    }
}
interface CallWatch {
    void call();
}
interface RecordSport {
    void recordSport();
}
class MyCallWatch extends DelegatingIntroductionInterceptor implements CallWatch, RecordSport {
    @Override
    public void call() {
        System.out.println("My watch can call phone...");
    }
    @Override
    public void recordSport() {
        System.out.println("My watch can record sports...");
    }
}
class MyNewFunctionAdvisor extends DefaultIntroductionAdvisor {
    public MyNewFunctionAdvisor(Advice advice) {
        super(advice, new IntroductionInfo() {
            @Override
            public Class<?>[] getInterfaces() {
                return new Class[] {CallWatch.class, RecordSport.class};
            }
        });
    }
}

My watch can showTime...
My watch can call phone...
My watch can record sports...

ProcxyFactory

posted @ 2021-11-06 16:53  Older&nbspSix  阅读(22)  评论(0)    收藏  举报