本篇基于Android Framework 8.0的源码

对ContentProvider的使用从根本上来说也是围绕着Binder IPC。跟使用其他系统服务类似,APP对ContentProvider的使用可以分成三部分:

  • Client APP如何从系统服务中获取到ContentProvider的调用接口
  • ContentProvider如何将自己的调用接口发布到系统服务当中。
  • Client APP调用ContentProvider接口的具体流程。

本文的核心便是Client APP如何从系统服务中获取到ContentProvider的调用接口

重要的数据对象

1. ContentProviderRecord

在记录ContentProvider重要信息的数据对象中,毫无疑问ContentProviderRecord是最重要的。无论是在ProcessRecord中记录本APP内已经发布的ContentProvider,还是在AMS中记录所有APP发布的ContentProvider。
ContentProviderRecord中记录的重要内容:

2. ProviderClientRecord

另外一个重要的则是ProviderClientRecord,用于在Client APP中缓存已经获得的ContentProvider信息(包括本地的ContentProvider)。在使用ContentProvider时,如果发现缓存中已经存在对应的ContentProvider信息,则不再向AMS请求获取ContentProvider接口,而是直接使用缓存中记录的IContentProvider接口。

ProviderClientRecord中记录的IContentProvider是由ContentProviderHolder传来,在AMS初始化ContentProviderHolder时会将其中的IContentProvider赋值为IContentProvider的代理对象——ContentProviderProxy。后续通过ContentResolver执行ContentProvider的CRUD操作时,便是通过ContentProviderProxy对象Binder IPC到实际的ContentProvider中。

获取ContentProvider的流程解析

在Client APP使用过程中,一般不会直接与ContentProvider进行通信,使用ContentProvider的请求都是交由ContentResolver来执行。平时获取ContentResolver都是通过Context的getContentResolver()来执行,最终都会执行的是ContextImpl中的getContentResolver():

//ContextImpl.java
public ContentResolver getContentResolver() {return mContentResolver;
}
复制代码

返回的是已经保存在Context中的mContentResolver,这一实例的初始化在ContextImpl的相关初始化函数当中:( 8.0在私有构造器中,4.2在init函数当中 )

//ContextImpl.java
...
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
...
复制代码

这里赋值的实例ApplicationContentResolver类型,这是一个ContentResolver接口的实现类,里边重写了获取和释放ContentProvider的逻辑

    @Overrideprotected IContentProvider acquireProvider(Context context, String auth) {return mMainThread.acquireProvider(context,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), true);}@Overrideprotected IContentProvider acquireExistingProvider(Context context, String auth) {return mMainThread.acquireExistingProvider(context,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), true);}@Overridepublic boolean releaseProvider(IContentProvider provider) {return mMainThread.releaseProvider(provider, true);}@Overrideprotected IContentProvider acquireUnstableProvider(Context c, String auth) {return mMainThread.acquireProvider(c,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), false);}@Overridepublic boolean releaseUnstableProvider(IContentProvider icp) {return mMainThread.releaseProvider(icp, false);}@Overridepublic void unstableProviderDied(IContentProvider icp) {mMainThread.handleUnstableProviderDied(icp.asBinder(), true);}@Overridepublic void appNotRespondingViaProvider(IContentProvider icp) {mMainThread.appNotRespondingViaProvider(icp.asBinder());}
复制代码

具体方法实现的内容只是相当于转发了一下请求到ActivityThread当中去执行具体的逻辑,由ActivityThread来负责同AMS进行交互。

acquireExistingProvideracquireProvideracquireUnstableProvider这三个方法最终都是转发执行的同一个方法ActivityThread.acquireProvider()。区别在于最后一个标识符,true代表stable provider,false代表unstable provider. 在ActivityThread中的实现:

final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);if (provider != null) {return provider;}IActivityManager.ContentProviderHolder holder = null;try {holder = ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);} catch (RemoteException ex) {}if (holder == null) {Slog.e(TAG, "Failed to find provider info for " + auth);return null;}holder = installProvider(c, holder, holder.info,true /*noisy*/, holder.noReleaseNeeded, stable);return holder.provider;复制代码

acquireProvider() 分为三部分:

  • 1.首先尝试获取缓存的IContentProvider。
  • 2.获取不到才通过AMS获取IContentProvider。
  • 3.获取成功后先执行ActivityThread.installProvider再返回provider给ContentResolver。

第一部分:获取缓存中的IContentProvider的逻辑:

        synchronized (mProviderMap) {final ProviderKey key = new ProviderKey(auth, userId);final ProviderClientRecord pr = mProviderMap.get(key);if (pr == null) {return null;}IContentProvider provider = pr.mProvider;IBinder jBinder = provider.asBinder();if (!jBinder.isBinderAlive()) {Log.i(TAG, "Acquiring provider " + auth + " for user " + userId+ ": existing object's process dead");handleUnstableProviderDiedLocked(jBinder, true);return null;}ProviderRefCount prc = mProviderRefCountMap.get(jBinder);if (prc != null) {incProviderRefLocked(prc, stable);}return provider;}
复制代码
  • 由于接口可能会被多线程调用,因此这里先加了锁。
  • 首先通过auth、userId从mProviderMap中获取provider。
  • 如果该provider所在的进程已经死亡则调用handleUnstableProviderDiedLocked,并返回null。
  • 在handleUnstableProviderDiedLocked()里,如果死亡的provider保存在需要释放的Provider列表里,则对其进行释放清理;并通知AMS该unstable provider已经死亡。
  • 如果保存在需要释放的Provider列表里,则对其引用计数+1。

第二部分:通过AMS.getContentProvider()获取IContentProvider接口

最后都在 AMS.getContentProviderImpl() 中执行。(这里是Blocking执行,lock是AMS。这把锁很多地方都用到,比方注册广播接收器时也是用的这把锁)

ActivityManagerService's getContentProviderImpl()

            long startTime = SystemClock.uptimeMillis();//获取Caller的进程信息ProcessRecord r = null;if (caller != null) {r = getRecordForAppLocked(caller);if (r == null) {throw new SecurityException("Unable to find app for caller " + caller+ " (pid=" + Binder.getCallingPid()+ ") when getting content provider " + name);}}boolean checkCrossUser = true;checkTime(startTime, "getContentProviderImpl: getProviderByName");//检查对应的ContentProvider是否已经存在cpr = mProviderMap.getProviderByName(name, userId);if (cpr == null && userId != UserHandle.USER_SYSTEM) {cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM);if (cpr != null) {cpi = cpr.info;if (isSingleton(cpi.processName, cpi.applicationInfo,cpi.name, cpi.flags)&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) {userId = UserHandle.USER_SYSTEM;checkCrossUser = false;} else {//不是单例则清空继续执行获取cpr = null;cpi = null;}}}// ContentProvider存在同时所在进程存在&该进程没被杀boolean providerRunning = cpr != null && cpr.proc != null && !cpr.proc.killed;// 后续的执行的内容分段讲解......
复制代码

AMS会先尝试获取缓存的ContentProvider信息,并判断该ContentProvider是否保持运行,并以此为根据来进行后续的执行。后续方法体的执行分为三部分:

1.ContentProvider保持运行

if (providerRunning) {cpi = cpr.info;String msg;checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))!= null) {throw new SecurityException(msg);}checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");//如果provider已经发布或者正在发布则直接返回。if (r != null && cpr.canRunHere(r)) {ContentProviderHolder holder = cpr.newHolder(null);holder.provider = null;return holder;}try {if (AppGlobals.getPackageManager().resolveContentProvider(name, 0 /*flags*/, userId) == null) {return null;}} catch (RemoteException e) {}final long origId = Binder.clearCallingIdentity();checkTime(startTime, "getContentProviderImpl: incProviderCountLocked");// 增加引用计数conn = incProviderCountLocked(r, cpr, token, stable);if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {//更新LRUcheckTime(startTime, "getContentProviderImpl: before updateLruProcess");updateLruProcessLocked(cpr.proc, false, null);checkTime(startTime, "getContentProviderImpl: after updateLruProcess");}}checkTime(startTime, "getContentProviderImpl: before updateOomAdj");final int verifiedAdj = cpr.proc.verifiedAdj;//更新进程adjboolean success = updateOomAdjLocked(cpr.proc, true);if (success && verifiedAdj != cpr.proc.setAdj && !isProcessAliveLocked(cpr.proc)) {success = false;}maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name);checkTime(startTime, "getContentProviderImpl: after updateOomAdj");if (DEBUG_PROVIDER) Slog.i(TAG_PROVIDER, "Adjust success: " + success);//进程已经被杀if (!success) {//确保自己不为同时杀掉Slog.i(TAG, "Existing provider " + cpr.name.flattenToShortString()+ " is crashing; detaching " + r);//减少引用计数boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);checkTime(startTime, "getContentProviderImpl: before appDied");appDiedLocked(cpr.proc);checkTime(startTime, "getContentProviderImpl: after appDied");if (!lastRef) {// This wasn't the last ref our process had on// the provider...  we have now been killed, bail.return null;}providerRunning = false;conn = null;} else {cpr.proc.verifiedAdj = cpr.proc.setAdj;}Binder.restoreCallingIdentity(origId);}
复制代码

关键的步骤都在上面有中文注释,值得注意的是如果减少了引用计数之后,Client和ContentProider之间还有Connection,会导致Client进程被杀掉,所以直接返回空。

2.ContentProvider没有在运行

            if (!providerRunning) {try {checkTime(startTime, "getContentProviderImpl: before resolveContentProvider");//先尝试从PackageManager中获取ContentProviderInfocpi = AppGlobals.getPackageManager().resolveContentProvider(name,STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);checkTime(startTime, "getContentProviderImpl: after resolveContentProvider");} catch (RemoteException ex) {}if (cpi == null) {return null;}boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,cpi.name, cpi.flags)&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid);if (singleton) {userId = UserHandle.USER_SYSTEM;}cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);checkTime(startTime, "getContentProviderImpl: got app info for user");String msg;checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))!= null) {throw new SecurityException(msg);}checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");if (!mProcessesReady&& !cpi.processName.equals("system")) {throw new IllegalArgumentException("Attempt to launch content provider before system ready");}//如果provider的提供用户没有在运行则直接返回。if (!mUserController.isUserRunningLocked(userId, 0)) {Slog.w(TAG, "Unable to launch app "+ cpi.applicationInfo.packageName + "/"+ cpi.applicationInfo.uid + " for provider "+ name + ": user " + userId + " is stopped");return null;}ComponentName comp = new ComponentName(cpi.packageName, cpi.name);checkTime(startTime, "getContentProviderImpl: before getProviderByClass");//尝试从通过包名从缓存中获取ContentProvidercpr = mProviderMap.getProviderByClass(comp, userId);checkTime(startTime, "getContentProviderImpl: after getProviderByClass");final boolean firstClass = cpr == null;//没有缓存在进入处理,尝试获取新的ContentProviderRecord.if (firstClass) {final long ident = Binder.clearCallingIdentity();if (mPermissionReviewRequired) {if (!requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) {return null;}}try {checkTime(startTime, "getContentProviderImpl: before getApplicationInfo");//根据包名和userId尝试获取新的ApplicationInfo.ApplicationInfo ai =AppGlobals.getPackageManager().getApplicationInfo(cpi.applicationInfo.packageName,STOCK_PM_FLAGS, userId);checkTime(startTime, "getContentProviderImpl: after getApplicationInfo");//安装包中获取不到对应的信息,直接返回if (ai == null) {Slog.w(TAG, "No package info for content provider "+ cpi.name);return null;}ai = getAppInfoForUser(ai, userId);//封装在新的ContentProviderRecordcpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);} catch (RemoteException ex) {// pm is in same process, this will never happen.} finally {Binder.restoreCallingIdentity(ident);}}checkTime(startTime, "getContentProviderImpl: now have ContentProviderRecord");if (r != null && cpr.canRunHere(r)) {//如果ContentProvider声明了多进程运行或者处于同一个进程或者拥有同样的uid, 则直接返回。return cpr.newHolder(null);}if (DEBUG_PROVIDER) Slog.w(TAG_PROVIDER, "LAUNCHING REMOTE PROVIDER (myuid "+ (r != null ? r.uid : null) + " pruid " + cpr.appInfo.uid + "): "+ cpr.info.name + " callers=" + Debug.getCallers(6));//如果该ContentProvider已经正在启动中,则i < 正在启动的ContentProvider数。final int N = mLaunchingProviders.size();int i;for (i = 0; i < N; i++) {if (mLaunchingProviders.get(i) == cpr) {break;}}//没有在启动中,启动它。if (i >= N) {final long origId = Binder.clearCallingIdentity();try {//设置包状态try {checkTime(startTime, "getContentProviderImpl: before set stopped state");AppGlobals.getPackageManager().setPackageStoppedState(cpr.appInfo.packageName, false, userId);checkTime(startTime, "getContentProviderImpl: after set stopped state");} catch (RemoteException e) {} catch (IllegalArgumentException e) {Slog.w(TAG, "Failed trying to unstop package "+ cpr.appInfo.packageName + ": " + e);}//获取对应的进程信息checkTime(startTime, "getContentProviderImpl: looking for process record");ProcessRecord proc = getProcessRecordLocked(cpi.processName, cpr.appInfo.uid, false);if (proc != null && proc.thread != null && !proc.killed) {if (DEBUG_PROVIDER) Slog.d(TAG_PROVIDER,"Installing in existing process " + proc);if (!proc.pubProviders.containsKey(cpi.name)) {checkTime(startTime, "getContentProviderImpl: scheduling install");//provider对应的进程已经启动,但是该provider还没有被publish//因此发送消息触发publish provider的操作。proc.pubProviders.put(cpi.name, cpr);try {proc.thread.scheduleInstallProvider(cpi);} catch (RemoteException e) {}}} else {//provider所在的进程没有启动,或者已经被杀//启动provider对应的进程checkTime(startTime, "getContentProviderImpl: before start process");proc = startProcessLocked(cpi.processName,cpr.appInfo, false, 0, "content provider",new ComponentName(cpi.applicationInfo.packageName,cpi.name), false, false, false);checkTime(startTime, "getContentProviderImpl: after start process");if (proc == null) {//无法启动,直接返回空Slog.w(TAG, "Unable to launch app "+ cpi.applicationInfo.packageName + "/"+ cpi.applicationInfo.uid + " for provider "+ name + ": process is bad");return null;}}//将ContentProviderRecord中保存的launchingApp设置为所在的进程,并将provider//保存到正在启动的ContentProvider列表cpr.launchingApp = proc;mLaunchingProviders.add(cpr);} finally {Binder.restoreCallingIdentity(origId);}}checkTime(startTime, "getContentProviderImpl: updating data structures");//保存新的ContentProviderRecord到mProviderMap中。if (firstClass) {mProviderMap.putProviderByClass(comp, cpr);}mProviderMap.putProviderByName(name, cpr);//增加Client与ContentProvider之间的引用计数。conn = incProviderCountLocked(r, cpr, token, stable);if (conn != null) {conn.waiting = true;}}
复制代码

关键步骤在文中有注释,主要流程是:

  • 权限检查
  • 通过PM获取对应provider信息
  • 检查provider是否能够正常运行
  • 启动对应的进程,然后publish provider
  • 更新包状态、缓存、引用计数。

3. 轮询确保provider被publish完成

        synchronized (cpr) {while (cpr.provider == null) {//provider所在进程已经不存在则直接返回if (cpr.launchingApp == null) {Slog.w(TAG, "Unable to launch app "+ cpi.applicationInfo.packageName + "/"+ cpi.applicationInfo.uid + " for provider "+ name + ": launching app became null");EventLog.writeEvent(EventLogTags.AM_PROVIDER_LOST_PROCESS,UserHandle.getUserId(cpi.applicationInfo.uid),cpi.applicationInfo.packageName,cpi.applicationInfo.uid, name);return null;}try {if (DEBUG_MU) Slog.v(TAG_MU,"Waiting to start provider " + cpr+ " launchingApp=" + cpr.launchingApp);//Client与ContentProvider之间有引用时才执行if (conn != null) {conn.waiting = true;}cpr.wait();} catch (InterruptedException ex) {} finally {if (conn != null) {conn.waiting = false;}}}}return cpr != null ? cpr.newHolder(conn) : null;
复制代码

概要总结getContentProviderImpl是如何实现获取ContentProvider: 总是先尝试从缓存中获取已经publish的provider,并返回;如果缓存中不存在才尝试创建新的provider并启动对应的进程。

第三部分:执行ActivityThread.installProvider()

   private ContentProviderHolder installProvider(Context context,ContentProviderHolder holder, ProviderInfo info,boolean noisy, boolean noReleaseNeeded, boolean stable) {ContentProvider localProvider = null;IContentProvider provider;if (holder == null || holder.provider == null) {//本地providerif (DEBUG_PROVIDER || noisy) {Slog.d(TAG, "Loading provider " + info.authority + ": "+ info.name);}Context c = null;ApplicationInfo ai = info.applicationInfo;//provider与caller在同一个包内if (context.getPackageName().equals(ai.packageName)) {c = context;} else if (mInitialApplication != null &&mInitialApplication.getPackageName().equals(ai.packageName)) {c = mInitialApplication;} else {try {c = context.createPackageContext(ai.packageName,Context.CONTEXT_INCLUDE_CODE);} catch (PackageManager.NameNotFoundException e) {// Ignore}}if (c == null) {Slog.w(TAG, "Unable to get context for package " +ai.packageName +" while loading content provider " +info.name);return null;}if (info.splitName != null) {try {c = c.createContextForSplit(info.splitName);} catch (NameNotFoundException e) {throw new RuntimeException(e);}}//创建本地providertry {final java.lang.ClassLoader cl = c.getClassLoader();localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();provider = localProvider.getIContentProvider();if (provider == null) {Slog.e(TAG, "Failed to instantiate class " +info.name + " from sourceDir " +info.applicationInfo.sourceDir);return null;}if (DEBUG_PROVIDER) Slog.v(TAG, "Instantiating local provider " + info.name);// XXX Need to create the correct context for this provider.//创建本地ContentProvider的ContextlocalProvider.attachInfo(c, info);} catch (java.lang.Exception e) {if (!mInstrumentation.onException(null, e)) {throw new RuntimeException("Unable to get provider " + info.name+ ": " + e.toString(), e);}return null;}} else {//来自本App外的ContentProviderprovider = holder.provider;if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "+ info.name);}ContentProviderHolder retHolder;synchronized (mProviderMap) {if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider+ " / " + info.name);IBinder jBinder = provider.asBinder();if (localProvider != null) {//本地provider分支ComponentName cname = new ComponentName(info.packageName, info.name);ProviderClientRecord pr = mLocalProvidersByName.get(cname);if (pr != null) {//缓存中已经存在本地provider实例。if (DEBUG_PROVIDER) {Slog.v(TAG, "installProvider: lost the race, "+ "using existing local provider");}provider = pr.mProvider;} else {//不存在,创建新的Client provider record并install,//然后保存到本地的缓存中holder = new ContentProviderHolder(info);holder.provider = provider;holder.noReleaseNeeded = true;pr = installProviderAuthoritiesLocked(provider, localProvider, holder);mLocalProviders.put(jBinder, pr);mLocalProvidersByName.put(cname, pr);}//返回值为install过的本地Content Provider HolderretHolder = pr.mHolder;} else {ProviderRefCount prc = mProviderRefCountMap.get(jBinder);if (prc != null) {//不为空,说明本地provider 引用缓存中已经保存有该provider的引用计数。if (DEBUG_PROVIDER) {Slog.v(TAG, "installProvider: lost the race, updating ref count");}//外部provider则增加引用计数。if (!noReleaseNeeded) {incProviderRefLocked(prc, stable);try {ActivityManager.getService().removeContentProvider(holder.connection, stable);} catch (RemoteException e) {//do nothing content provider object is dead any way}}} else {ProviderClientRecord client = installProviderAuthoritiesLocked(provider, localProvider, holder);if (noReleaseNeeded) {//本地provider或者来自系统的providerprc = new ProviderRefCount(holder, client, 1000, 1000);} else {//其他App的providerprc = stable? new ProviderRefCount(holder, client, 1, 0): new ProviderRefCount(holder, client, 0, 1);}//在本App的缓存中保存provider的引用计数。mProviderRefCountMap.put(jBinder, prc);}retHolder = prc.holder;}}return retHolder;}
复制代码

对于本地ContentProvider, installProvider做的事情是先创建一个本地provider,并将相应的内容(IContentProvider接口、本地ContentProvider实例等)保存到本App的缓存当中;对于外部的ContentProvider,installProvider做的事情则是在本App的缓存当中保存provider的引用计数。

转载于:https://juejin.im/post/5a9a57ef5188255589494058

ContentProvider解析-获取ContentProvider接口相关推荐

  1. springboot 获取访问接口的请求的IP地址

    工具类: import javax.servlet.http.HttpServletRequest; import java.net.InetAddress; import java.net.Unkn ...

  2. 微信小程序python解析获取用户手机号_微信小程序获取用户手机号

    获取微信用户绑定的手机号,需先调用wx.login接口. 小程序获取code. 后台得到session_key,openid. 组件触发getPhoneNumber 因为需要用户主动触发才能发起获取手 ...

  3. keycloak的access_token解析 用于后端接口鉴权

    keycloak的access_token解析 用于后端接口鉴权 keycloak 获取token https://{}:{}/auth/realms/{realm}/protocol/openid- ...

  4. c# typescript_在任何IDE中从C#,Java或Python代码获取TypeScript接口的简单方法

    c# typescript by Leonardo Carreiro 莱昂纳多·卡雷罗(Leonardo Carreiro) 在任何IDE中从C#,Java或Python代码获取TypeScript接 ...

  5. php7抓取网页数据,php7-远程获取api接口或网页内容

    在各类项目中经常要用到API调用,或是抓取对方网页内容,这里给大家一个远程获取API接口的PHP函数,函数返回一个数组,$result[0]为状态码,正常情况下是200,$result[1]为正常返回 ...

  6. 获取Java接口的所有实现类

    获取Java接口的所有实现类 前言:想看基于spring 的最简单实现方法,请直接看 第七步. 本文价值在于 包扫描的原理探究和实现 一.背景 项目开发中,使用Netty做服务端,保持长连接与客户端( ...

  7. java 获取接口的注解_java反射注解妙用-获取所有接口说明

    前言 最近在做项目权限,使用shiro实现restful接口权限管理,对整个项目都进行了重构.而权限管理需要用到所有的接口配置,包括接口url地址,接口唯一编码等.想要收集所有的接口信息,如果工程接口 ...

  8. Android中怎获取json,Android应用中如何解析获取的json数据

    Android应用中如何解析获取的json数据 发布时间:2020-11-24 17:10:08 来源:亿速云 阅读:107 作者:Leah 这篇文章将为大家详细讲解有关Android应用中如何解析获 ...

  9. 通过Java反射获取对象上的注解,java反射注解妙用-获取所有接口说明

    原标题:java反射注解妙用-获取所有接口说明 转载请注明出处:https://www.cnblogs.com/wenjunwei/p/10293490.html 前言 最近在做项目权限,使用shir ...

最新文章

  1. c语言安卓图形库cairo,cairo 图形库
  2. JavaScript 开发的40个经典技巧
  3. 算法分析与设计「三」二分算法
  4. 预培训个人项目(地铁线路规划)
  5. mfc radio group 设置
  6. 目录_计算机视觉中的数学方法
  7. 单片机音乐倒数计时器c语言,音乐倒数计时器单片机课程设计报告
  8. 本人对Oracle Bill Of Material模块的一些了解
  9. Unity简单爆炸效果的实现
  10. 有些人的微信字体可以变成蓝色,点进去就可以知道答案,这是为什么呢?
  11. 用 HLS m3u8 及FFMPEG搭建视频点播平台
  12. Honey Badger BFT(异步共识算法)笔记
  13. 英语总结系列(十六):这个四月真不错
  14. Trac - Trac Links
  15. 珍大户《认知世界的经济学》学习笔记 --第19课 时间补偿 第20课 利率 国债 MLF SLF OMO 利率
  16. 《scikit-learn机器学习》波斯顿房价预测(线性回归预测)
  17. 【心情分享】时间的魅力
  18. JAVA实现多边形游戏
  19. 关于2013年10大数字标牌行业发展趋势的预测与分析
  20. 1.js简介,JavaScript的组成,js环境搭建

热门文章

  1. matlab 基金业绩归因,5分钟搞定基金从业:绝对收益归因和相对收益归因
  2. 企业级基于Centos8.5配置IPXE服务批量部署windows方案
  3. 软件开发中 前台、中台、后台英文_你应该知道的“中台”相关知识
  4. 思科路由器重置密码的方法
  5. 马云个人名义捐款华为_扒一扒马云的捐款
  6. 使用hover给div加边框,出现div晃动和页面布局发生混乱的解决办法
  7. BrainNet Viewer脑皮层图(Volume)自定义每个模块的颜色
  8. Java的基础语法(基础语法大全)
  9. 浏览器如何验证SSL证书?
  10. 内置函数LPAD和RPAD函数