Java反射原理以及一些常见的应用

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 创建对象有四大方法,new、反射、克隆、反序列化创建,今天我们将由阿里技术专家 关键,围绕反射来展开分享。主要内容有类装载、反射原理、反射使用场景、AOP。

本文由内容志愿者整理阿里云社群直播而来。

讲师:关键

目录
image.png

一、类装载

我们都知道,java在编译类后并不是产生固有机器的机器码,而是一段字节码,这段字节码可以存放于任何地方,如.class文件,jar包中,可以通过网络传输。JVM虚拟机在拿取到这段二进制数据流字节码后,就会处理这些数据,并最终转换成一个java.lang.Class的实例(注意,这里并不是类本身的实例)。

java.lang.Class实例是访问类型元数据的接口,也是实现反射的关键数据。通过Class类提供的接口,可以访问一个类型的方法,字段等信息。

二进制数据流字节码被加载到虚拟机之后,会进行一系列的验证检查,主要步骤如下:

1.格式检查:包括魔数检查(识别文件类型开头的几个十六进制数,每一种文件类型都不同),版本检查,长度检查

2.语义检查:是否继承final,是否有父类,抽象方法是否有实现

3.字节码验证:跳转指令是否指向正确位置,操作数类型是否合理

4.符号引用验证:符号引用的直接引用是否存在

当一个类验证通过时,虚拟机就会进入准备阶段。分配内存空间,分配初始值。如果类存在常量字段,如果被final修饰,就会被直接放入常量池中。如果没有final修饰,就会在初始化中赋值,而不是直接放入常量池。

准备阶段完成后,就是解析类,解析类就是把字节码中的类,接口,字段,方法放入JVM虚拟机实际内存的地址中,方便程序可以真正执行,比如说类的方法会有一个方法表,当需要调用一个类的方法时,就要知道这个方法在方法表中的偏移量,直接调用该方法。

类的初始化是类装载的最后一个阶段。初始化的重要工作就是执行类的初始化方法。方法是由编译器自动生成的,它是由类静态成员的赋值语句以及static语句合并产生的。类似代码如下

public class SimpleStatic {
    public static int id = 1;
    public static int number;
    static {
        number = 4;
    }
}

反射代码示例如下:

public class ClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        Class clzStr = Class.forName("java.lang.String");
        //获取该类的所有方法
        Method[] ms = clzStr.getDeclaredMethods();
        for (Method m:ms) {
            //获取该方法的修饰符
            String mod = Modifier.toString(m.getModifiers());
            //打印方法的修饰符,方法名跟起始括号
            System.out.print(mod + " " + m.getName() + " (");
            //获取方法的所有参数类型
            Class<?>[] ps = m.getParameterTypes();
            //如果没有参数,直接打印结束括号
            if (ps.length == 0) {
                System.out.print(')');
            } else {
                //取出所有的参数类型名称,以逗号分隔
                for (int i = 0; i < ps.length; i++) {
                    char end = i == ps.length - 1 ? ')' : ',';
                    System.out.print(ps[i].getSimpleName() + end);
                }
            }
            System.out.println();
        }
    }
}

运行结果:

public equals (Object)
public toString ()
public hashCode ()
public compareTo (String)
public volatile compareTo (Object)
public indexOf (String,int)
public indexOf (String)
public indexOf (int,int)
public indexOf (int)
static indexOf (char[],int,int,char[],int,int,int)
static indexOf (char[],int,int,String,int)
public static valueOf (int)
public static valueOf (long)
public static valueOf (float)
public static valueOf (boolean)
public static valueOf (char[])
public static valueOf (char[],int,int)
public static valueOf (Object)
public static valueOf (char)
public static valueOf (double)
public charAt (int)
private static checkBounds (byte[],int,int)
public codePointAt (int)
public codePointBefore (int)
public codePointCount (int,int)
public compareToIgnoreCase (String)
public concat (String)
public contains (CharSequence)
public contentEquals (CharSequence)
public contentEquals (StringBuffer)
public static copyValueOf (char[])
public static copyValueOf (char[],int,int)
public endsWith (String)
public equalsIgnoreCase (String)
public static transient format (Locale,String,Object[])
public static transient format (String,Object[])
public getBytes (int,int,byte[],int)
public getBytes (Charset)
public getBytes (String)
public getBytes ()
public getChars (int,int,char[],int)
 getChars (char[],int)
private indexOfSupplementary (int,int)
public native intern ()
public isEmpty ()
public static transient join (CharSequence,CharSequence[])
public static join (CharSequence,Iterable)
public lastIndexOf (int)
public lastIndexOf (String)
static lastIndexOf (char[],int,int,String,int)
public lastIndexOf (String,int)
public lastIndexOf (int,int)
static lastIndexOf (char[],int,int,char[],int,int,int)
private lastIndexOfSupplementary (int,int)
public length ()
public matches (String)
private nonSyncContentEquals (AbstractStringBuilder)
public offsetByCodePoints (int,int)
public regionMatches (int,String,int,int)
public regionMatches (boolean,int,String,int,int)
public replace (char,char)
public replace (CharSequence,CharSequence)
public replaceAll (String,String)
public replaceFirst (String,String)
public split (String)
public split (String,int)
public startsWith (String,int)
public startsWith (String)
public subSequence (int,int)
public substring (int)
public substring (int,int)
public toCharArray ()
public toLowerCase (Locale)
public toLowerCase ()
public toUpperCase ()
public toUpperCase (Locale)
public trim ()

Class.forName可以得到代表String类的Class实例,它是反射中最重要的方法。

二、反射原理

反射离不开Class.forName(),我们先从Class.forName说起。
反射可以跟new一个对象有相同的效果。例如:

public class Company {
    private String a;
    private String b;

    @Override
    public String toString() {
        return "Company{" +
                "a='" + a + '\'' +
                ", b='" + b + '\'' +
                '}';
    }

    public Company() {
        this.a = "A";
        this.b = "B";
    }
}


public class CompanyInstance {
    private static Company company = new Company();

    public static void main(String[] args) {
        System.out.println(company);
    }
}

运行结果:

Company{a='A', b='B'}

又可以写成如下

public class CompanyInstance {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        System.out.println(Class.forName("com.guanjian.Company").newInstance());
    }
}

运行结果:

Company{a='A', b='B'}
new VS newInstance

虽然效果一样,但他们的过程并不一样。 首先,newInstance( )是一个方法,而new是一个关键字;其次,Class下的newInstance()的使用有局限,因为它生成对象只能调用无参的构造函数,而使用 new关键字生成对象没有这个限制。

newInstance()的时候是使用的上篇说的类装载机制的,它会走完全部过程。具体可以看 浅析类装载 ,而new一个实例的时候,走的流程不太一样,它会先在JVM内部先去寻找该类的Class实例,然后依照该Class实例的定义,依葫芦画瓢,把该类的实例给生成出来。

但如果找不到该类的Class实例,则会走上篇说的装载流程。 其中JDK的Class实例一般是在jvm启动时用启动类加载器完成加载,用户的Class实例则是在用到的时候再加载。

Class.forName()

Class.forName()被重载为有一个参数和三个参数的,我们来看一下其源码。

@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

无论是三参还是单参的都调用了此方法:

private static native Class<?> forName0(String name, boolean initialize,
                                        ClassLoader loader,
                                        Class<?> caller)
    throws ClassNotFoundException;

它的第二个参数boolean initialize表示是否要初始化该类,单参Class.forName()默认true是要初始化的,三参的Class.forName()由你自己选择。一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。当然如果你使用了三个参数的Class.forName(),并调用了newInstance()以后,是肯定会初始化的。

ClassLoader,这个才是真正装载类的核心组件。所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制字节码数据流读入系统,然后交给JVM虚拟机进行连接、初始化等操作。

ClassLoader的分类

在标准的Java程序中,Java虚拟机会创建3类ClassLoader为整个应用程序服务。它们分别是:BootStrap ClassLoader(启动类加载器),Extension ClassLoader(扩展类加载器),App ClassLoader(应用类加载器,也称为系统类加载器)。

此外,每一个应用程序还可以拥有自定义的ClassLoader,扩展Java虚拟机获取Class数据的能力。其中,应用类加载器的双亲为扩展类加载器,扩展类加载器的双亲为启动类加载器。

当系统需要使用一个类时,在判断类是否已经被加载时,会先从当前底层类加载器进行判断。当系统需要加载一个类时,会从顶层类开始加载,依次向下尝试,直到成功。

三、反射使用场景

1、编码阶段不知道需要实例化的类名是哪个,需要在runtime从配置文件中加载:

Class clazz = class.forName("xxx.xxx.xxx")
clazz.newInstance();

2、在runtime阶段,需要临时访问类的某个私有属性

ClassA objA = new ClassA();
Field xxx = objA.getClass().getDeclaredField("xxx")
xxx.setAccessible(true);

3、当使用标签的时候,我们要获取标签

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotionTest {
    String value() default "哈士奇";
}
@AnnotionTest
public class Dog {
    private String type;
    private String name;
    public Dog() {
        type = "金毛";
        name = "大黄";
    }
public Dog(String type,String name){
        this.type = type;
        this.name = name;
}

// toString()省略
}
public class CheckDog {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("com.guanjian.Dog");
        if (clazz.isAnnotationPresent(AnnotionTest.class)) {
            Field field = clazz.getDeclaredField("type");
            field.setAccessible(true);
            System.out.println(field.get(clazz.newInstance()));
            AnnotionTest test = (AnnotionTest)clazz.getAnnotation(AnnotionTest.class);
            System.out.println(test.value());
        }
    }
}

4、获取具体的构造器来构造类本身的实例

Class<?>[] constructorParams = {String.class,String.class};
        Constructor<?> cons = clazz.getConstructor(constructorParams);
        System.out.println(cons.newInstance("哈士奇","神哈"));

5、其他(可以用到的地方还有很多,比如获取父类的方法,判断是不是一个接口等等)

四、AOP

AOP主要是通过动态代理实现的,动态代理分两种,一种是JDK的,一种是CGlib。JDK的动态代理必须有一个接口。

public interface Greeting {
    void sayHello(String name);
}

还有一个实现类,是实现了它的主要功能。

public class GreetingImpl implements Greeting {
    public void sayHello(String name) {
        System.out.println("Hello! " + name);
    }
}
public class DynamicProxy<T> implements InvocationHandler {
    private Object target;

    public DynamicProxy(Object target) {
        this.target = target;
    }
    //获取目标对象的代理类实例
    public T getProxy() {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),  //目标对象类的加载器
                target.getClass().getInterfaces(),  //目标对象类的接口数组(因为一个类可能有几个接口)
                this
        );
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target,args);
        after();
        return result;
    }
    private void before() {
        System.out.println("Before");
    }
    private void after() {
        System.out.println("After");
    }
}

main方法

public class Client {
    public static void main(String[] args) {
        Greeting greetingProxy = new DynamicProxy<Greeting>(new GreetingImpl()).getProxy();
        greetingProxy.sayHello("Jack");
    }
}

运行结果:

Before
Hello! Jack
After

动态代理是通过反射来实现的,这里有一个Proxy.newProxyInstance,我们来看一下它的源代码。

//代理类的构造器的参数类型数组
private static final Class<?>[] constructorParams =
    { InvocationHandler.class };
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    //要求动态代理类本身不为空,如果为空会抛出异常
    Objects.requireNonNull(h);
    //将接口数组克隆出来
    final Class<?>[] intfs = interfaces.clone();
    //启动Java安全管理器
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        //检查调用类是否有启动接口的Class实例的加载器的权限
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     * 创建代理类的Class实例
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            //检查调用类是否有该代理类的权限
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        //通过构造参数类型来获取代理类的具体某一个构造器
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        //代理类本身实例
        final InvocationHandler ih = h;
        //获取代理类Class实例的所有修饰符,并判断是否为public,如果不为public则通过cons.setAccessible(true)设置为可以访问
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        //通过该具体的构造器来构造代理类本身的实例,之前我们有说过Class的newInstance只能通过无参构造器来构造,但此处是选择了具体的有参构造器构造
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

由以上代码可知,该动态代理必须要有接口才能代理,而不能代理没有接口的类。

没有接口的类,我们可以使用CGLib来动态代理,要使用CGLib,先要在pom中加入它的引用

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
public class CGLibDynamicProxy<T> implements MethodInterceptor {
    private static CGLibDynamicProxy instance = new CGLibDynamicProxy();

    private CGLibDynamicProxy() {
    }

    /**
     * 通过单例模式获取代理类的实例
     * @return
     */
    public static CGLibDynamicProxy getInstance() {
        return instance;
    }

    /**
     * 通过实现类的Class实例跟代理类对象关联
     * @param cls
     * @return
     */
    public T getProxy(Class<T> cls) {
        return (T)Enhancer.create(cls,this);
    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o,objects);
        after();
        return result;
    }
    private void before() {
        System.out.println("Before");
    }
    private void after() {
        System.out.println("After");
    }
}

main方法

public class Client {
    public static void main(String[] args) {
        Greeting greetingProxy = (Greeting) CGLibDynamicProxy.getInstance().getProxy(GreetingImpl.class);
        greetingProxy.sayHello("Jack");
    }
}

我们来简单看一下Enhancer.create的源码

public static Object create(Class type, Callback callback) {
    Enhancer e = new Enhancer();  //创建一个增强代理类对象,它可以拦截被代理的除final,static以外的所有类
    e.setSuperclass(type);  //设置该代理类的父类为实现类
    e.setCallback(callback);  //设置实现了MethodInterceptor接口的回调类,这里即为我们动态代理类本身的实例,因为MethodInterceptor是继承于Callback的接口,它是一个
                              //对象方法拦截器
    return e.create();       //返回实现类的增强类(所谓增强即可以对这个类进行拦截和各种处理操作)
}

了解了动态代理之后,我们来了解一下Spring+AspectJ的AOP。我们主要结合拦截指定注解(@annotation)的方式来大概说明一下用法。

比如说我们要拦截一个自定义的标签,对标有该标签的方法,记录日志,通过MQ(具体不限定哪种MQ)发送到日志中心,进行存储。

首先我们有一个日志的实体类。

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Log implements Serializable {

   private static final long serialVersionUID = -5398795297842978376L;

   private Long id;
   private String username;
   /** 模块 */
   private String module;
   /** 参数值 */
   private String params;
   private String remark;
   private Boolean flag;
   private Date createTime;
   private String ip;
   private String area;
}

有一个自定义的标签,这是一个方法级标签。

/**
 * 日志注解
 *
 */
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {

   String module();

   /**
    * 记录参数<br>
    * 尽量记录普通参数类型的方法,和能序列化的对象
    * 
    * @return
    */
   boolean recordParam() default true;
}

增加日志所属的模块(用户操作)。

/**
 * 日志模块定义
 *
 */
public abstract class LogModule {

    public static final Map<String, String> MODULES = new HashMap<>();

    public static final String LOGIN = "LOGIN";
    public static final String LOGOUT = "LOGOUT";

    public static final String ADD_PERMISSION = "ADD_PERMISSION";
    public static final String UPDATE_PERMISSION = "UPDATE_PERMISSION";
    public static final String DELETE_PERMISSION = "DELETE_PERMISSION";

    public static final String ADD_ROLE = "ADD_ROLE";
    public static final String UPDATE_ROLE = "UPDATE_ROLE";
    public static final String DELETE_ROLE = "DELETE_ROLE";
    public static final String SET_PERMISSION = "SET_PERMISSION";

    public static final String SET_ROLE = "SET_ROLE";
    public static final String UPDATE_USER = "UPDATE_USER";
    public static final String UPDATE_ME = "UPDATE_ME";

    public static final String UPDATE_PASSWORD = "UPDATE_PASSWORD";
    public static final String RESET_PASSWORD = "RESET_PASSWORD";

    public static final String ADD_MENU = "ADD_MENU";
    public static final String UPDATE_MENU = "UPDATE_MENU";
    public static final String DELETE_MENU = "DELETE_MENU";
    public static final String SET_MENU_ROLE = "SET_MENU_ROLE";

    public static final String ADD_BLACK_IP = "ADD_BLACK_IP";
    public static final String DELETE_BLACK_IP = "DELETE_BLACK_IP";

    public static final String FILE_UPLOAD = "FILE_UPLOAD";
    public static final String FILE_DELETE = "FILE_DELETE";

    public static final String ADD_MAIL = "ADD_MAIL";
    public static final String UPDATE_MAIL = "UPDATE_MAIL";
    public static final String DELETE_USER = "DELETE_USER";

    static {
        MODULES.put(LOGIN, "登陆");
        MODULES.put(LOGOUT, "退出");

        MODULES.put(ADD_PERMISSION, "添加权限");
        MODULES.put(UPDATE_PERMISSION, "修改权限");
        MODULES.put(DELETE_PERMISSION, "删除权限");

        MODULES.put(ADD_ROLE, "添加角色");
        MODULES.put(UPDATE_ROLE, "修改角色");
        MODULES.put(DELETE_ROLE, "删除角色");
        MODULES.put(SET_PERMISSION, "分配权限");
        MODULES.put(SET_ROLE, "分配角色");

        MODULES.put(UPDATE_USER, "修改用户");
        MODULES.put(UPDATE_ME, "修改个人信息");
        MODULES.put(UPDATE_PASSWORD, "修改密码");
        MODULES.put(RESET_PASSWORD, "重置密码");

        MODULES.put(ADD_MENU, "添加菜单");
        MODULES.put(UPDATE_MENU, "修改菜单");
        MODULES.put(DELETE_MENU, "删除菜单");
        MODULES.put(SET_MENU_ROLE, "分配菜单");

        MODULES.put(ADD_BLACK_IP, "添加黑名单");
        MODULES.put(DELETE_BLACK_IP, "删除黑名单");

        MODULES.put(FILE_UPLOAD, "文件上传");
        MODULES.put(FILE_DELETE, "文件删除");

        MODULES.put(ADD_MAIL, "保存邮件");
        MODULES.put(UPDATE_MAIL, "修改邮件");
        MODULES.put(DELETE_USER, "删除用户");

    }

}

然后我们定义一个AOP的实现类。

@Aspect
public class LogAop {

    private static final Logger logger = LoggerFactory.getLogger(LogAop.class);
    //对@LogAnnotation标签进行环绕增强
    @Around(value = "@annotation(com.cloud.model.log.LogAnnotation)")
    public Object logSave(ProceedingJoinPoint joinPoint) throws Throwable { //ProceedingJoinPoint joinPoint连接点,可以通过该对象获取方法的任务信息,例如,方
        Log log = new Log();                                                //法名,参数等。
        log.setCreateTime(new Date());
        LoginAppUser loginAppUser = AppUserUtil.getLoginAppUser();
        if (loginAppUser != null) {
            log.setUsername(loginAppUser.getUsername());
        }
        //通过连接点获取方法签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //通过方法的签名获取方法的标签
        LogAnnotation logAnnotation = methodSignature.getMethod().getDeclaredAnnotation(LogAnnotation.class);
        log.setModule(logAnnotation.module());
        //我们是否要收集方法的参数,默认为true
        if (logAnnotation.recordParam()) {
            String[] paramNames = methodSignature.getParameterNames();// 获取方法参数名
            if (paramNames != null && paramNames.length > 0) {
                Object[] args = joinPoint.getArgs();// 通过连接点获取传入参数值
                //定义一个HashMap,并将对应的参数名和参数值全部放入该HashMap
                Map<String, Object> params = new HashMap<>();
                for (int i = 0; i < paramNames.length; i++) {
                    params.put(paramNames[i], args[i]);
                }

                try {
                    log.setParams(JSONObject.toJSONString(params)); //Json序列化对应参数信息放入log对象中
                } catch (Exception e) {
                    logger.error("记录参数失败:{}", e.getMessage());
                }
            }
        }

        try {
            Object object = joinPoint.proceed();// 执行原方法
            //执行成功处理
            log.setFlag(Boolean.TRUE);

            return object;
        } catch (Exception e) {
            //执行失败处理
            log.setFlag(Boolean.FALSE);
            log.setRemark(e.getMessage());
            throw e;
        } finally {
            // 异步将Log对象发送到队列
            CompletableFuture.runAsync(() -> {
                try {
                    /////////此处可以将日志对象log发送到消息队列,由于在一些系统中,一些用户的操作大量且频繁,推荐使用Kafka来作为推送日志的消息队列,比如
                    /////////商品购买,退货等等,在LogModule中添加对应的操作标志。
                    logger.info("发送日志到队列:{}", log);
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            });

        }

    }
}

因为@Aspect标签是未被Spring托管的,所以要对LogAop实行自动配置,在资源文件夹下的spring.factories中

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cloud.log.autoconfigure.LogAop

有多条以, 分隔成多行。在一些自己开发的jar包导入新的Spring boot工程中,此种方法是最为有效的依赖注入方式。

比如在用户模块要修改个人信息的时候

/**
 * 修改自己的个人信息
 *
 * @param appUser
 * @return
 */
@LogAnnotation(module = LogModule.UPDATE_ME)
@PutMapping("/users/me")
public Map<String, Object> updateMe(@RequestBody AppUser appUser) {
    AppUser user = AppUserUtil.getLoginAppUser();
    appUser.setId(user.getId());

    appUserService.updateAppUser(appUser);

    return ResponseUtils.getDataResult(user);
}

这个updateMe方法就会被AOP环绕增强,将操作的信息,传递参数放入log对象,并通过MQ发送到日志中心进行接收。其实这里面接入点的具体实现也是通过反射来处理的。而AOP的整个实现也是通过动态代理来实现的。当然不要忘了在本模块内的配置文件中增加。

spring:
  aop:  
    proxy-target-class: true

它的默认值是false,默认只能代理接口(使用JDK动态代理),当为true时,才能代理目标类(使用CGLib动态代理)。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
人工智能 安全 Java
Java和Python在企业中的应用情况
Java和Python在企业中的应用情况
62 7
|
10天前
|
Java 数据库连接 Spring
反射-----浅解析(Java)
在java中,我们可以通过反射机制,知道任何一个类的成员变量(成员属性)和成员方法,也可以堆任何一个对象,调用这个对象的任何属性和方法,更进一步我们还可以修改部分信息和。
|
13天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
26 3
|
13天前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
44 2
|
2月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
163 6
|
1月前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
37 2
|
2月前
|
监控 Java
Java基础——反射
本文介绍了Java反射机制的基本概念和使用方法,包括`Class`类的使用、动态加载类、获取方法和成员变量信息、方法反射操作、以及通过反射了解集合泛型的本质。同时,文章还探讨了动态代理的概念及其应用,通过实例展示了如何利用动态代理实现面向切面编程(AOP),例如为方法执行添加性能监控。
|
2月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
69 6
|
2月前
|
关系型数据库 MySQL Java
MySQL索引优化与Java应用实践
【11月更文挑战第25天】在大数据量和高并发的业务场景下,MySQL数据库的索引优化是提升查询性能的关键。本文将深入探讨MySQL索引的多种类型、优化策略及其在Java应用中的实践,通过历史背景、业务场景、底层原理的介绍,并结合Java示例代码,帮助Java架构师更好地理解并应用这些技术。
57 2
|
2月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####