1. Annotation and reflection

Annotations and reflections

1. Annotation

1.1. What are annotations?

Like when we implement a method of an interface, there will be an @Override annotation. Annotation is to explain the program,

It is no different from our annotations on methods and classes, but annotations can be read by other programs for information processing,

Otherwise it's not much different from annotations.

1.2, built-in annotations

Built-in annotations are some of the annotations that our jdk brings.

Three commonly used annotations:

  • @Override

    A rhetorical method indicating an intention to override a method declaration in a superclass

  • @Deprecated

    When using a method, a horizontal line will appear, indicating that it is abandoned. This annotation can modify methods, properties, and classes, indicating that programmers are not encouraged to use such elements.

    Usually because he's dangerous or has better options

  • @SuperWarnings

    This annotation is mainly used to suppress warning messages. When we write programs, we may report a lot of yellow line warnings.

    But without affecting the operation, we can use this annotation to suppress and hide it. Unlike the previous two annotations, we

    An annotation parameter must be given to use it correctly.

parameter illustrate
deprecation Warning about using an obsolete class or method
unchecked Warning when unchecked conversion is performed eg: Unspecified generics when using collections
fallthrough case penetration occurs when used in a switch statement
path Warning of non-existing path in classpath, source file path
serial Warning when serialVersionUID definition is missing on serialized class
finally Warning if any finally clause cannot be completed
all Warning about all the above

In the above table are some parameters of the @SuperWarnings annotation, which can be used as needed

@SuperWarnings(“finally”)

@SuperWarnings(value={“unchecked”,“path”})

1.3, custom annotations

格式:public @interface 注解名{定义体}
  • When using the @interface custom annotation, the java.lang.annotation, Annotation interface is automatically inherited
  • Each of these methods actually declares a configuration parameter
  • The name of the method is the name of the parameter
  • The return value type is the type of the parameter (the return value type can only be the basic type, Class, String, enum)
  • You can use default to declare the default value of the parameter
  • If there is only one parameter member, the general parameter name is value
  • We must have a value when using the annotation element, you can define a default value, an empty string, 0 or -1
public @interface TestAnnotation{
	// 参数默认为“”
	String value() default "";
}

1.4. Meta annotations

When we customize annotations, we need to use the meta-annotations provided by java, which are other annotations responsible for annotations. java definition

four standard meta-annotation types, they are used to provide declarations for other annotation types

  • @Target

    The function of this annotation is mainly to describe the scope of use of the annotation. To put it bluntly, it is where the annotations defined by ourselves can be used.

    Modified range valueElementType
    package PACKAGE
    Classes, Interfaces, Enumerations, Annotation Types TYPE
    Type members (methods, constructors, member variables, enumeration values) CONSTRUCTOR: Used to describe the constructor. FIELD: Used to describe the field. METHOD: used to describe the method
    Method parameters and local variables LOCAL_VARIABLE: Used to describe local variables. PARAMETER: used to describe parameters

    We customize an annotation, and we can see all the constants that provide us when declaring the meta-annotation

  • @Retention

    Indicates at what level the annotation information needs to be saved to describe the life cycle of the annotation, that is, where our annotation is still valid

    (SOURCE < CLASS < RUNTIME)

  • @Documented

    Indicates that the annotation will be included in the javadoc

  • @Inherited

    Indicates that subclasses can inherit the annotation from the parent class

package com.hou.study;

import java.lang.annotation.*;

@MyAnnotation
public class Test02 {
    public void test(){}
}

// 表示注解的作用域
@Target(value = ElementType.TYPE)

// 表示注解在运行时还有效
@Retention(value = RetentionPolicy.RUNTIME)

// 表示该注解被包含在javadoc中
@Documented

// 该注解表示子类可以继承父类的注解
@Inherited
@interface MyAnnotation{

}

2, Reflection

2.1. What is reflection?

Reflection refers to classes that we can load, detect, and use at runtime that are completely unknown at compile time.

​ is a dynamic mechanism that allows us to instruct programs to instantiate, manipulate properties, and call methods through strings.

Using code increases flexibility, but it also brings more resource overhead.

After the class is loaded, an object of type Class is generated in the heap memory (a class has only one Class object),

This object contains the complete class structure information. We can see the structure of the class through this object. This object is like a mirror,

Through this mirror to see the structure of the class, so we call it: reflection.

2.2, class class

When we use reflection, we need to get the class we need first, and the class java.lang.Class is essential, it is very special, it is used to represent the type in java

(class/interface/enum/annotation/primitive type/void)本身。

  • An object of class Class contains the structure of a loaded class. A loaded class corresponds to a Class object.
  • When a class is loaded, or when the loader (classloader) defineClass() is called by the JVM, the JVM automatically generates a Class object.

Get an object of class Class

First create a normal entity class

package com.hou.reflection;

// 实体类
public class User{
    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. Obtained by Class.forName()
package com.hou.reflection;

public class Test {
    public static void main(String[] args) {

        try {
            // 通过反射获取类的Class对象  forName()
            Class c1 = Class.forName("com.hou.reflection.User");
            System.out.println(c1);

            Class c2 = Class.forName("com.hou.reflection.User");
            Class c3 = Class.forName("com.hou.reflection.User");
            Class c4 = Class.forName("com.hou.reflection.User");

            // 一个类在内存中只有一个Class对象
            // 一个类被加载后,类的整个结构都会被封装在class对象中
            System.out.println(c1.hashCode());
            System.out.println(c2.hashCode());
            System.out.println(c3.hashCode());
            System.out.println(c4.hashCode());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}
  1. Get through getClass()
package com.hou.reflection;

public class Test02 {
    public static void main(String[] args) {
        // 通过getClass来获取
        User user = new User();
        Class c1 = user.getClass();
        System.out.println(c1);
    }
}

  1. Get through .class
package com.hou.reflection;

public class Test03 {
    public static void main(String[] args) {
        // 通过.class 获取
        Class c1 = User.class;
        System.out.println(c1);
    }
}

2.3, all types of Class objects

package com.hou.reflection;

import java.lang.annotation.ElementType;

// 所有类型的Class对象
public class Test04 {
    public static void main(String[] args) {
        Class c1 = Object.class; // Object类
        Class c2 = Comparable.class; // 接口
        Class c3 = String[].class; // 一维数组
        Class c4 = int[][].class; // 二维数组
        Class c5 = Override.class; // 注解
        Class c6 = ElementType.class; // 枚举
        Class c7 = Integer.class; // 基本数据类型
        Class c8 = void.class; // void
        Class c9 = Class.class; // Class

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);

        int[] a = new int[10];
        int[] b = new int[100];
        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());
    }
}

2.4, class loading memory analysis

Class loading and understanding of ClassLoader
  1. Loading: Load the bytecode content of the class file into memory, convert these static data into the runtime data structure of the method area, and then generate a java.lang.Class object representing the class

  2. Link: The process of incorporating the binary code of a Java class into the running state of the JVM

    1. Validation: Make sure the loaded class information conforms to the JVM specification and there are no security issues
    2. Preparation: The stage of formally allocating memory for class variables (static) and setting the default initial value of class variables, these memory will be allocated in the method area
    3. Resolution: The process of replacing symbolic references (constant names) in the virtual machine constant pool with direct references (addresses)
  3. initialization:

    • The process of executing the class constructor () method, the class constructor () method is the assignment action that automatically collects all class variables in the class by the compiler

    ​ is combined with the statements in the static code block. (The class constructor is for constructing class information, not the constructor for constructing objects of this class)

    • When initializing a class, if you find that its parent class has not been initialized, you need to trigger the initialization of its parent class first

    • The virtual machine ensures that a class's () method is properly locked and synchronized in a multithreaded environment

package com.hou.reflection;

public class Test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);

        /**
         * 1.加载到内存,会产生一个类对应的class对象
         * 2.链接,链接结束后 m = 0
         * 3.初始化
         *      <clinit>(){
         *          System.out.println("A类静态代码快初始化");
         *          m = 300;
         *          m = 100;
         *      }
         */
    }
}

class A{
    static {
        System.out.println("A类静态代码快初始化");
        m = 300;
    }

    static int m = 100;

    public A() {
        System.out.println("A类的无参构造初始化");
    }
}

2.5, analysis class initialization

When does the initialization of a class happen?

  • Active reference to the class (initialization of the class must occur)
    • When the virtual machine starts, first initialize the class where the main method is located
    • new object of a class
    • call static members (except final constants) and static methods of a class
    • Use the methods of the java.lang.reflect package to make reflection calls to classes
    • When initializing a class, if its parent class has not been initialized, its parent class will be initialized first
  • Passive reference to the class (initialization of the class does not occur)
    • When accessing a static field, only the class that actually declares the field will be initialized. For example: when the static variable of the parent class is referenced through the subclass, it will not cause the subclass to initialize
    • Defining a class reference via an array does not trigger initialization of this class
    • Reference constants do not trigger initialization of this class (constants are stored in the constant pool of the calling class during the linking phase)
package com.hou.reflection;

public class Test06 {
    static {
        System.out.println("main类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        // 1.主动引用
        // Son son = new Son();

        // 反射也会产生主动引用
        // Class.forName("com.hou.reflection.Son");

        // 不会产生类的引用的方法
        // System.out.println(Son.b); // 子类调用父类,没有产生子类的加载

        // Son[] array = new Son[5]; // 只有main类被加载

        System.out.println(Son.M); // 父类和子类都没被加载
    }
}

class Father{
    static int b = 2;

    static {
        System.out.println("父类被加载");
    }
}

class Son extends Father{
    static {
        System.out.println("子类被加载");
        m = 300;
    }

    static int m = 100;
    static final int M = 1;
}

2.6, the role of class loader

  • The role of class loading: load the bytecode content of the class file into memory, and convert these static data into the runtime data structure of the method area,

    ​ Then generate a java.lang.Class object representing this class in the heap as the access entry for class data in the method area

  • Class Cache: The standard JavaSE class loader can look up classes on demand, but once a class is loaded into the class loader, it will remain loaded (cached) for a period of time.

    However, the JVM garbage collection mechanism can recycle these Class objects

  • The role of a class loader is to load classes into memory. The JVM specification defines loaders for the following types of classes

    • Bootstrap class loader : Written in C++, it is the class loader that comes with the JVM and is responsible for the core library of the Java platform and used to load the core class library. This loader cannot be obtained directly
    • Extended class loader : Responsible for jar packages in the jre/lib/ext directory or jar packages in the directory specified by -D java.ext.dirs into the working library
    • System class loader : responsible for the packaging of classes and jars in the directory pointed to by java —classpath or —D java.class.path, and is the most commonly used loader
package com.hou.reflection;

public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException {
         // 获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        // 获取系统类加载器的父类加载器--》扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);

        // 获取扩展类加载器的父类加载器--》根加载器(C/C++)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);

        // 测试当前类是哪个加载器加载的
        ClassLoader classLoader = Class.forName("com.hou.reflection.Test07").getClassLoader();
        System.out.println(classLoader);

        // 测试JDK内置的类是谁加载的
        classLoader = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader);

        // 如何获得系统类加载器可以加载的路径
        System.out.println(System.getProperty("java.class.path"));


    }
}

2.7. Get the runtime structure of a class

Get the complete structure of the runtime class via reflection:

Field、Method、Constructor、Superclass、Interface、Annotation

package com.hou.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

// 获取类的信息
public class Test08 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("com.hou.reflection.User");

        // User user = new User();
        // c1 = user.getClass();

        // 获得类的名字
        System.out.println(c1.getName()); // 获得包名 + 类目
        System.out.println(c1.getSimpleName()); // 获得类名

        System.out.println("===================");
        // 获得类的属性
        Field[] fields = c1.getFields(); // 获得 public属性

        fields = c1.getDeclaredFields(); // 获得全部属性
        for (Field field : fields) {
            System.out.println(field);
        }

        // 获得指定属性的值
        System.out.println("======================");
        Field name = c1.getDeclaredField("name");
        System.out.println(name);

        // 获得类的方法
        System.out.println("===================");
        Method[] methods = c1.getMethods();
        for (Method method : methods) {
            System.out.println("正常的:" + method);
        }

        Method[] declaredMethods = c1.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("getDeclaredMethods:" + declaredMethod);
        }

        // 获得指定方法   因为可能有重载,所以需要参数   (指定方法,方法参数)
        Method getName = c1.getMethod("getName", null);
        Method setName = c1.getMethod("setName", String.class);
        System.out.println(getName);
        System.out.println(setName);

        // 获得构造器
        System.out.println("======================");
        Constructor[] constructors = c1.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        constructors = c1.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }

        // 获得指定的构造器
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class);
        System.out.println("指定的构造器:" + constructor);
    }
}

2.8. Dynamically create object execution method

  • Create an object of a class: call the newInstance() method of the Class object
      1. The class must have a parameterless constructor
      2. Class constructor access rights need to be sufficient
  • Proceed as follows:
      1. Obtain the constructor of the specified parameter type of this class through getDeclaredConstructor(Class … parameterTypes) of the Class class
      2. Pass an array of objects into the constructor's formal parameters
      3. Instantiate objects through Constructor
  • Object invoke(Object obj, Object … args)
    • Object corresponds to the return value of the original method. If the original method has no return value, it returns null.
    • If the original method is a static method, then the formal parameter Object obj can be null
    • Object[] args is null if the original method parameter list is empty
    • If the original method is declared as private, you need to display the setAccessible(true) method of the calling method object before calling this invoke() method, so that the private method can be accessed
  • setAccessible(boolean flag)
    • Method and Field and Constructor objects have setAccessible() methods
    • setAccessible is a switch to enable and disable access security checks
    • A parameter value of true indicates that the reflected object should cancel the Java language access check when it is used.
      • Improve the efficiency of reflection. If reflection must be used in the code, and the code needs to be called frequently, please set it to true
      • Make otherwise inaccessible private members accessible
    • A parameter value of false indicates that the reflected object should implement Java language access checks
package com.hou.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 动态的创建对象,通过反射
public class Test09 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class c1 = Class.forName("com.hou.reflection.User");

        // 构造一个对象
        User user = (User) c1.newInstance();  // 本质上是调用了类的无参构造器
        System.out.println(user);

        // 通过构造器创建对象
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class);
        User user1 = (User) constructor.newInstance("liu", 18);
        System.out.println(user1);

        // 通过反射调用普通方法
        User user2 = (User) c1.newInstance();
        // 通过反射调用一个方法
        Method setName = c1.getDeclaredMethod("setName", String.class);

        // invoke: 激活的意思
        // (对象,“方法的值”)
        setName.invoke(user2,"侯");
        System.out.println(user2.getName());

        // 通过反射操作属性
        User user3 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");

        // 不能直接操作私有属性 , 我们需要关闭程序的安全检测 , 属性或者方法的setAccessible(true)
        name.setAccessible(true); // 取消安全检查
        name.set(user3,"鹏");
        System.out.println(user3.getName());


    }
}

2.9, performance test analysis

Turning off detection increases reflection efficiency

setAccessible(true);

package com.hou.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test10 {

    // 普通方式调用
    public void test01(){
        User user = new User();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式执行10亿次:" + (endTime-startTime) + "ms");
    }
    // 反射方式调用
    public void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null); // invoke 激活
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式执行10亿次:" + (endTime-startTime) + "ms");
    }
    // 反射方式调用  关闭检测
    public void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        long startTime = System.currentTimeMillis();
        Method getName = c1.getMethod("getName", null);
        getName.setAccessible(true);
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("关闭检测的反射方式调用:" + (endTime-startTime) + "ms");
    }
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        Test10 test10 = new Test10();
        test10.test01();
        test10.test02();
        test10.test03();
    }
}

2.10, reflection operation generic

  • Java adopts the mechanism of generic erasure to introduce generics. The generics in Java are only used by the compiler javac to ensure data security and

    Eliminate the problem of forced type conversion, but once compiled, all types related to generics are erased

  • To manipulate these types through reflection, Java adds ParameterizedType, GenericArrayType, TypeVariable and WildcardType

    Several types to represent types that cannot be normalized to the Class class but have the same name as the original type

    • ParameterizedType: Represents a parameterized type, such as Collection
    • GenericArrayType: represents an array type whose element type is a parameterized type or type variable
    • TypeVariable: is the common parent interface of various types of variables
    • WildcardType: represents a wildcard type expression
package com.hou.reflection;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class Test11 {

    public void test01(Map<String,User> map, List<User> list){
        System.out.println("test01");
    }

    public Map<String,User> test02(){
        System.out.println("test02");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test11.class.getMethod("test01", Map.class, List.class);

        Type[] genericParameterTypes = method.getGenericParameterTypes(); // 获得方法的参数类型

        for (Type genericParameterType : genericParameterTypes) {
            System.out.println(genericParameterType);
            if (genericParameterType instanceof ParameterizedType){
                // 获得真实参数信息
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

        Method method1 = Test11.class.getMethod("test02",null);

        Type genericReturnType = method1.getGenericReturnType(); // 获取返回值类型

        if (genericReturnType instanceof ParameterizedType){
            // 获得真实参数类型
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
}


2.11, reflection operation annotation

  • getAnnotations
  • getAnnotation

Exercise : ORM

  • Know what is ORM?
    • Object relationship Mapping --> Object relationship mapping
    • class and table structure correspondence
    • attribute and field correspondence
    • Object and record correspondence
  • Requirement: Use annotation and reflection to complete the mapping relationship between class and table structure
package com.hou.reflection;

import java.lang.annotation.*;
import java.lang.reflect.Field;

// 练习反射操作注解
public class Test12 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("com.hou.reflection.Student2");

        // 通过反射获得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        // 获得注解 value 的值
        TableHou tableHou = (TableHou) c1.getAnnotation(TableHou.class);
        String value = tableHou.value();
        System.out.println(value);

        // 获得类指定的注解
        Field f = c1.getDeclaredField("id");
        FieldHou annotation = f.getAnnotation(FieldHou.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}

@TableHou("db_student")
class Student2{

    @FieldHou(columnName = "db_id",type = "int",length = 10)
    private int id;
    @FieldHou(columnName = "db_age",type = "int",length = 10)
    private int age;
    @FieldHou(columnName = "db_name",type = "varchar",length = 3)
    private String name;

    public Student2() {
    }

    public Student2(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student2{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

// 类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableHou{
    String value();
}

// 属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldHou{
    String columnName();
    String type();
    int length();
}

summary

  • Java.Annotation annotation

    • The role of Annotation:

      • Not the program itself, the program can be interpreted. (This is no different from a comment)

      • Can be read by other programs (eg: compilers, etc.)

    • Built-in annotations

    • custom annotation

    • meta-annotation

  • Java.Reflection

    • Related APIs:
      • java.lang.Class : represents a class
      • java.lang.reflect.Method: represents the method of the class
      • java.lang.reflect.Field: a member variable representing a class
      • java.lang.reflect.Constructor: the constructor representing the class
    • Class loading process:
      • Class loading (Load) reads the class's class file into memory and creates a java.lang.Class object for it. This process is done by the class loader
      • The class link (Link) merges the binary data of the class into the JRE
      • Class initialization (Initialize) JVM is responsible for initializing the class and initializing the execution () method
        • If the parent class is not initialized, trigger the initialization of the parent class
    • newInstance() creates an object of the class
    • Object invoke(Object obj, Object[] args) 激活
    • setAccessible(true) cancels the security check
  • Parent delegation mechanism: ClassLoader (class loader) is to hand over the request to the parent class for processing, it is a task delegation mechanism (look up)

    • Advantage:
      • Avoid duplicate loading of classes
      • protect program security

Guess you like

Origin blog.csdn.net/weixin_56121715/article/details/123829868