LayoutInflater学习(一)之布局解析

news2024/10/7 4:25:51

LayoutInflater的创建与实例化

LayoutInflater是位于 "android.view" 包下的一个抽象类,同样它也是一个系统级服务

package android.view;
@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {

LayoutInflater是用来解析 xml 布局文件,并创建对应的View对象,不管是系统级的布局文件还是开发者自己定义的xml布局文件都要用它来解析,一般通过下面的代码来解析布局.

LayoutInflater.from(this).inflate(R.layout.layout_normal_group, ll, false);

前面说了LayoutInflater是一个抽象类,那么它的实现类是谁,它又是怎么实例化的呢,其实去看LayoutInflater类的介绍也可以知道,因为LayoutInflater是一个系统服务,所以可以直接通过对应的Context上下文来获取LayoutInflater对象

LayoutInflater LayoutInflater =
         (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

除此之外还可以通过 LayoutInflater.from(context) 或者 Activity.getLayoutInflater,其实这两种获取方法最终还是通过上面的 getSystemService() 方法来获取的,知道了怎么获取LayoutInflater实例,但是LayoutInflater毕竟是一个抽象类,它的实现类到底是谁?

LayoutInflater的实现类是 PhoneLayoutInflater,其实它也就是重写了一个LayoutInflater的onCreateView()方法,并提供了一个cloneInContext()方法来客隆一个LayoutInflater实例

package com.android.internal.policy;
/**
 * @hide
 */
public class PhoneLayoutInflater extends LayoutInflater {
    ......
    public LayoutInflater cloneInContext(Context newContext) {
        return new PhoneLayoutInflater(this, newContext);
    }
}

PhoneLayoutInflater的创建是在SystemServiceRegistry中完成的,具体的细节这次就不再深究了

package android.app;
@SystemApi
public final class SystemServiceRegistry {
    ......
    static {
        ......
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                 new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
        ......
    }

LayoutInflater的解析过程

接下来看下LayoutInflater解析xml布局的流程,上篇中提到了DecorView解析系统布局的一个方法

final View root = inflater.inflate(layoutResource, null);

再加上上文提到的另一个inflate方法,主要也就是使用这两个常用方法解析加载布局

LayoutInflater.from(this).inflate(R.layout.layout_normal_group, ll, false);

现在直接从这个方法入手看下解析的详细流程

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
     return inflate(resource, root, root != null);
}

可以看到主要流程还是在inflate(int,ViewGroup,boolean)方法里,直接看主要流程

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        ......
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ......
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
            try {
                //通过XmlPullParser找到布局文件中tag为XmlPullParser.START_TAG的位置
                advanceToRootNode(parser);
                final String name = parser.getName();
                ......
                if (TAG_MERGE.equals(name)) {
                    ......
                } else {
                    // Temp is the root view that was found in the xml
                    //创建 xml 布局中最外层的View对象
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    rInflateChildren(parser, temp, attrs, true);
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
               ......
            }
            return result;
        }
    }

首先 advanceToRootNode(XmlPullParser parser) 方法是通过XmlPullParser解析xml布局,找到xml布局中tag标签为"XmlPullParser.START_TAG"的位置,对于这个过程的具体流程我在之前的这篇文章里有记录:Android LayoutInflater inflate方法学习,有兴趣的可以去看下。

重点关注下inflate方法中的两个参数 root 参数以及 attachToRoot 参数

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 

看字面意思也可以理解, root 是我们解析某个xml布局时传入的一个父容器ViewGroup,该ViewGroup并不属于xml布局,一般是用来存放你要解析的xml布局的一个父容器,例如我们经常使用的RecyclerView在它的适配器的onCreateViewHolder中经常传入的parent参数就是root参数。这里的parent参数就是对应的RecyclerView,还有另一个参数 attachToRoot,看字面意思理解就是:是否要将解析后的xml布局依附于 root 容器,也就是把解析后的xml布局放入到 root 容器中。

接下来通过具体代码来理解下这两个参数:

1. root 不为空, attachToRoot 为 false

if (TAG_MERGE.equals(name)) {
   ......
 } else {
   final View temp = createViewFromTag(root, name, inflaterContext, attrs);
   ViewGroup.LayoutParams params = null;
   if (root != null) {
      params = root.generateLayoutParams(attrs);
      if (!attachToRoot) {
         //root不为空,attachToRoot为false时的第一个作用:
         //设置了一下xml最外层View(ViewGroup)的LayoutParams
         temp.setLayoutParams(params);
      }
   }
   rInflateChildren(parser, temp, attrs, true);
   if (root != null && attachToRoot) {
      root.addView(temp, params);
   }
   if (root == null || !attachToRoot) {
      //root不为空,attachToRoot为false时的第二个作用:
      //inflate方法最终返回的ViewGroup(View)是xml布局的最外层的ViewGroup(View)
      result = temp;
   }
}

总结:当 root 不为空且 attachToRoot 为false时,inflate方法最终返回的View是xml布局中最外层的View,并且这个View的 LayoutParams 被设置了一下,也就是手动设置了该View的宽、高。

下面来通过一个简单的例子看下这种情况下解析的布局是什么效果,例子很简单

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  LinearLayout ll = findViewById(R.id.ll);
  View view = LayoutInflater.from(this).inflate(R.layout.layout_normal, ll, false);
  FrameLayout ff = findViewById(R.id.ff);
  ff.addView(view);
}

 来看下 activity_main.xml 和 layout_normal.xml 的布局

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/cs"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/ll"
        android:background="@color/purple_200"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.21">
    </LinearLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <FrameLayout
        android:id="@+id/ff"
        android:background="@color/purple_700"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.76">
    </FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

layout_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView
    android:text="测试LayoutInflater inflate方法"
    android:layout_width="wrap_content"
    android:layout_height="50dp"
    android:background="@color/teal_200"
    android:textColor="@color/white"
    xmlns:android="http://schemas.android.com/apk/res/android" />

 看下最终的效果

2. root 不为空, attachToRoot 为 true

if (TAG_MERGE.equals(name)) {
   ......
 } else {
   final View temp = createViewFromTag(root, name, inflaterContext, attrs);
   ViewGroup.LayoutParams params = null;
   if (root != null) {
      params = root.generateLayoutParams(attrs);
      if (!attachToRoot) {
         //root不为空,attachToRoot为true这里就不再执行了
         temp.setLayoutParams(params);
      }
   }
   rInflateChildren(parser, temp, attrs, true);
   if (root != null && attachToRoot) {
      //root不为空,attachToRoot为true时,xml的根视图View直接被
      //添加到 root 容器里了,并最终返回 root
      root.addView(temp, params);
   }
   if (root == null || !attachToRoot) {
      //root不为空,attachToRoot为true这里也不再执行了
      result = temp;
   }
}

总结:

(1). 当 attachToRoot 为 true 时会直接通过 root.addView(temp,params) 的方法把 整个xml布局添加到 root 容器里

(2). inflate方法最后返回的是 root ,而不是原来的 xml 布局最外层的View

还是上面 1 中的例子,我们如果仅仅把 attachToRoot 的参数改为true,然后直接运行就会报下面的错误信息,报错位置是在 MainAcitvity 中:ff.addView(view);

Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
        at android.view.ViewGroup.addViewInner(ViewGroup.java:5235)
        at android.view.ViewGroup.addView(ViewGroup.java:5064)
        at android.view.ViewGroup.addView(ViewGroup.java:5004)
        at android.view.ViewGroup.addView(ViewGroup.java:4976)
        at com.github.layoutinflaterdemo.MainActivity.onCreate(MainActivity.java:22)

上面报错的原因就是因为:一个View只能有一个父亲,当我们通过 LayoutInflater.from(this).inflate(R.layout.layout_normal, ll, true); 来加载xml布局时,由于 attachToRoot为true,xml布局的最外层View已经被add到root中一次了,已经有了一个父View。所以现在对原来的onCreate中的代码做一下修改,修改后的代码如下: 

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  ConstraintLayout cs = findViewById(R.id.cs);
  LinearLayout ll = findViewById(R.id.ll);
  View view = LayoutInflater.from(this).inflate(R.layout.layout_normal, ll, true);
  FrameLayout ff = findViewById(R.id.ff);
  cs.removeView(view);
  ff.addView(view);
}

这里要注意的是,因为inflate方法中 attachToRoot 参数传的是 true,所以上面代码中inflate方法返回的view应该是id为 ll 的LinearLayout,而不再是之前的TextView了,这里 view 的 parent 是ConstraintLayout,所以需要先执行 cs.removeView(View); 最后看下效果。

 可以看到被添加的view最外层确实是原来的LinearLayout

3. root 为空的情况

if (TAG_MERGE.equals(name)) {
   if (root == null || !attachToRoot) {
      throw new InflateException("<merge /> can be used only with a 
      valid ViewGroup root and attachToRoot=true");
   }
   rInflate(parser, root, inflaterContext, attrs, false);
 } else {
   final View temp = createViewFromTag(root, name, inflaterContext, attrs);
   ViewGroup.LayoutParams params = null;
   ......
   rInflateChildren(parser, temp, attrs, true);
   ......
   if (root == null || !attachToRoot) {
      //这里root为空时不管attachToRoot是true还是false最终都返回temp
      result = temp;
   }
}

总结:当需要加载的xml布局最外层标签不是<merge>时,如果 root 为空,不管 attachToRoot 是 true 还是 false,inflate 方法都将返回 xml布局的最外层View。

目录

LayoutInflater的创建与实例化

LayoutInflater的解析过程

1. root 不为空, attachToRoot 为 false

2. root 不为空, attachToRoot 为 true

3. root 为空的情况


但是如果要加载的xml布局最外层标签是 <merge>,当 root 为空时, 将直接抛出异常

下面看事例,还是对原来的例子做下简单的修改

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  FrameLayout ff = findViewById(R.id.ff);
  View view = LayoutInflater.from(this).inflate(R.layout.layout_normal, null);

  ff.addView(view);
}

 最终效果:

细心的网友可能发现了问题,layout_normal 布局中只有一个TextView,并且xml中给的宽是“wrap_content”,高是固定值 50dp,而看最终效果明显跟xml布局中的宽高不一致,并且此时不管你怎么修改TextView的宽、高,最终的效果始终不变,看这个效果倒是像"match_parent"。 

原因分析:通过观察上面第3种情况时的源码就可以知道,当 root 为null时,我们通过LayoutInflater的 inflate 方法加载布局时,在 inflate方法中创建完成xml中最外层的View时,并没有为该View设置布局参数就直接返回了,所以此时 xml 布局中最外层布局的参数LayoutParams是无效的,也就是我们在 xml 中给最外层View设置的宽、高的参数是无效的。 

既然 root 为null时,通过LayoutInflater加载的布局,最外层View的宽、高的设置是无效的,为何上面的事例中最后的效果确像是"match_parent"? 这是因为:我们通过addView()方法向一个父容器中添加子View时,如果被添加的子View LayoutParams为空,这个时候会直接调用ViewGroup的方法 generateDefaultLayoutParams()来生产默认的布局参数,上面的事例是通过FrameLayout来添加的,我们看下FrameLayout的源码,发现FrameLayout重写了这个方法

FrameLayout.java

@Override
protected LayoutParams generateDefaultLayoutParams() {
   return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}

可以看到在 FrameLayout 中该方法默认给的宽高值,确实都是 "match_parent"。

由此我们可以得出:当使用 LayoutInflater 的 inflate()方法加载布局,并且 root 参数传了 null 时,我们如果还想要把解析后的 xml 布局添加到某个父容器中时,一定要给该布局最外层的View设置一个布局参数 LayoutParams,我们看下之前提到的DecorView中是怎么使用的

DecorView.java

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
   ......
   mDecorCaptionView = createDecorCaptionView(inflater);
   //root为null时,需要为生成的View添加 LayoutParams
   final View root = inflater.inflate(layoutResource, null);
   if (mDecorCaptionView != null) {
       if (mDecorCaptionView.getParent() == null) {
         addView(mDecorCaptionView,
                new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
       }
       mDecorCaptionView.addView(root,
              new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    } else {
      addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}

可以看到DecorView中在addView时都设置了对应的布局参数

同样的我们也来修改下之前的代码 

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  FrameLayout ff = findViewById(R.id.ff);
  View view = LayoutInflater.from(this).inflate(R.layout.layout_normal, null);
  ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,dp2px(50));
  view.setLayoutParams(params);
  ff.addView(view);
}

int dp2px(final float dpValue) {
  final float scale = this.getResources().getDisplayMetrics().density;
  return (int) (dpValue * scale + 0.5f);
}

再来看下最终的效果,是不是达到了期望效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/154448.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

桌面录屏软件,分享3个十分便捷的录屏软件

​图片和视频都是人们传播信息的方式&#xff0c;不过相比于图片&#xff0c;视频能够更加直观生动的表达信息。在日常生活中&#xff0c;除了屏幕上记录一些应用程序的内容外&#xff0c;有时我们还需要记录电脑桌面。有更好的桌面录屏软件吗&#xff1f;当然&#xff0c;小编…

Git操作不规范,战友提刀来相见!

年终奖都没了&#xff0c;还要扣我绩效&#xff0c;门都没有&#xff0c;哈哈。 这波骚Git操作我也是第一次用&#xff0c;担心闪了腰&#xff0c;所以不仅做了备份&#xff0c;也做了笔记&#xff0c;分享给大家。 文末留言抽奖&#xff0c;聊聊你的年终奖。 问题描述 小A和…

C++多线程(并发、进程、线程的基本概念和综述)

并发、进程、线程的基本概念和综述 并发 并发表示两个或者更多任务(独立的活动)同时发生(进行)。例如&#xff0c;一面唱歌一面弹琴&#xff0c;一面走路一面说话&#xff0c;画画的时候听小说等。回归到计算机领域&#xff0c;所谓并发&#xff0c;就是一个程序同时执行多个…

html 3D立体多形态旋转音乐相册 | 2022都结束了,还不快给女神制作一个特殊的纪念相册

&#x1f4cb; 前言 &#x1f5b1; 博客主页&#xff1a;在下马农的碎碎念✍ 本文由在下马农原创&#xff0c;首发于CSDN&#x1f4c6; 首发时间&#xff1a;2023/01/07&#x1f4c5; 最近更新时间&#xff1a;2023/01/07&#x1f935; 此马非凡马&#xff0c;房星本是星。向前…

零基础掌握IP地址知识,小白必学知识点!

前言 大家好&#xff0c;在生活中我们使用具有上网功能的电子设备都有IP地址&#xff0c;就跟每个人都有自己的名字一样。IP地址分为IPV4 IPV6&#xff0c;我们所说的的IP地址指的是IPV4的地址。 正文 IPV4( Internet Protocol Version 4 )互联协议版本4&#xff0c;有版本V4之…

【HTML+CSS+JavaScript】动感爱心—— I love you~

有段时间没有分享了,no time。 还是抽出一会儿分享一下。有时间会解析的(具体…I don’t know)。 1. 效果展示 真实效果挺好看的,喜欢的朋友,可以给你的女朋友或者喜欢的TA看看呀! 可以根据实际情况修改文案,比如诗歌,或者你爱的人的名字哦~ 2. 源代码分享 2.1 动感…

在Ubuntu上安装docker(Ubuntu版本18.04)

在Ubuntu上安装docker详细步骤1、卸载之前的docker版本2、安装docker仓库3、在系统中添加Docker的官方密钥4、添加docker源5、再次更新源列表6、查看可以安装的docker版本并安装docker7、使用命令查看是否安装成功以及安装的docker版本8、启动 docker服务并设置开机自动启动doc…

FPGA基础之modelsim常见问题

目录 问题一&#xff1a;modelsim破解失败 1&#xff09;现象 2&#xff09;原因 ​ 3&#xff09; 解决 问题一&#xff1a;modelsim破解失败 1&#xff09;现象 modelsim激活失败&#xff0c;原先正常使用过的&#xff0c;重新卸载安装破解&#xff0c;设置环境变量…

Serverless 奇点已来,下一个十年将驶向何方?

本文整理自 QCon 上海站 2022 丁宇&#xff08;叔同&#xff09;的演讲内容。 以前构建应用&#xff0c;需要买 ECS 实例&#xff0c;搭建开源软件体系然后维护它&#xff0c;流量大了扩容&#xff0c;流量小了缩容&#xff0c;整个过程非常复杂繁琐。 用了 Serverless 服务以…

【如何添加本地jar包到maven依赖】

如何添加本地jar包到maven依赖 1、本地jar包和对应依赖如下图&#xff08;刚开始这俩依赖是报红的&#xff09; 2、执行mvn命令如下&#xff1a; mvn install:install-file -DfileD:\ht_mesis-platform\mesis-business\dandian4-1.0.0.jar -Dpackagingjar -DgroupIddandian4…

Java实战:Hutool类库中的DateUtil用法总结

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️荣誉&#xff1a; CSDN博客专家、数据库优质创作者&#x1f3c6;&…

微信小程序 | 一比一复刻抖音短视频

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;小程序开发成神之路 --【这是一个为想要入门和进阶小程序开发专门开启的精品专栏&#xff01;从个人到商业的全套开发教程&#xff0c;实打实的干货分享&#xff0c;确定不来看看&#xff1f; …

Java中常用API总结(6)——BigInteger类

BigInteger类一、前言二、概述1.API帮助文档2.使用方法三、常见方法1.构造方法1️⃣格式2️⃣实例2.成员方法1️⃣格式2️⃣实例四、注意事项五、结语一、前言 平时在存储整数的时候&#xff0c;Java中默认是int类型&#xff0c;int类型有取值范围&#xff1a;-2147483648 ~ 2…

【深入理解JVM】内存模型

目录 运行时数据区域 程序计数器 Java虚拟机栈 本地方法栈 Java堆 方法区 运行时常量池 直接内存 虚拟机对象探秘 对象的创建 对象的内存布局 运行时数据区域 程序计数器 程序计数器是一块较小的内存空间&#xff0c;存储当前线程所执行的字节码指令的地址。在java…

C#开发的资源文件程序(可国际化) - 开源研究系列文章

上次将小软件的线程池描述了&#xff0c;也将插件程序描述了&#xff0c;这次就将里面的资源文件相关的内容进行下记录&#xff0c;这里能够让程序做成国际化的形式(即多语言程序)&#xff0c;主要就是通过这个资源文件的方式进行的处理。下面将对这个资源文件的定义进行描述&a…

多线程之waitnotify

目录&#xff1a; 前言 1.wait()方法 2 notify()方法 3.wait & notify的代码示例&#xff1a; 4.关于notifyAll()方法 前言 线程最大的问题就是抢占式执行&#xff0c;随机调度。虽然线程在操作系统内核里的调度是随机的&#xff0c;但是可以通过一些办法来控制线程…

带你了解SVG标签

✍️ 作者简介: 前端新手学习中。 &#x1f482; 作者主页: 在主页中查看更多前端教学&#xff0c;可接大学生前端作业单。 &#x1f393; 专栏分享&#xff1a;css重难点教学 Node.js教学 从头开始学习 ajax学习 js学习 目录初始SVG矩形&#xff0c;圆形和椭圆型 矩形 圆…

云安全(云安全数据中心、WAF、DDOS)

安全 安全威胁 可用性 安全威胁&#xff1a;大规模分布式拒绝服务攻击(DDoS)、僵尸网络(Botnet) 影响&#xff1a;网站业务不可用 完整性 安全威胁&#xff1a;网站入侵、服务器口令暴力破解 影响&#xff1a;网站页面被篡改和植入后门 保密性 安全威胁&#xff1a;网站后门…

二、Groovy入门

文章目录二、Groovy入门2.1 Groovy 简介2.2 Groovy 安装[非必须]2.3 IDEA创建 Groovy 项目2.4 Groovy 基本语法2.4.1 案例 1:基本注意点2.4.2 案例 2:引号说明2.4.3 案例 3:三个语句结构2.4.4 案例 4:类型及权限修饰符2.4.5 案例 5:集合操作2.4.6 案例 6:类导入2.4.7 案例 7:异…

MySQL是怎么保证主备一致的?

在前面的文章中,我不止一次地和你提到了 binlog,大家知道 binlog 可以用来归档,也可以用来做主备同步,但它的内容是什么样的呢?为什么备库执行了 binlog 就可以跟主库保持一致了呢?今天我就正式地和你介绍一下它。 毫不夸张地说,MySQL 能够成为现下最流行的开源数据库,…