从源码角度拆解SpringSecurity系列文章

从源码角度拆解SpringSecurity之核心流程
从源码角度拆解SpringSecurity之万能的SecurityBuilder
从源码角度拆解SpringSecurity之勤劳的FilterChainProxy
从源码角度拆解SpringSecurity之C位的AuthenticationManager


文章目录

  • 从源码角度拆解SpringSecurity系列文章
  • 前言
  • 注意
  • 一、C位的AuthenticationManager
  • 二、忠实的卫士AbstractSecurityInterceptor
  • 三、自定义数据权限鉴权框架
    • 获取权限配置和数据源
    • 加入与之对应的voter
    • 切入接口进行鉴权操作

前言

上一篇我们简单拆解了运行时的大致流程,知道了拦截动作都来自于FilterChainProxy,以及内部的Filter链怎么来的,顺序的重要性,以及如何分发的,还针对所学习内容介绍了一个简单但实际的小需求——如何自定义异常返回数据。本章我们将基于框架中处于C位的AuthenticationManager为切入点,对框架的认证和授权两个核心步骤进行拆解以及数据权限校验框架的实现。


注意

文章所摘源码版本为spring-boot-starter-security:5.0.6.RELEASEspring-security-oauth2:2.3.5.RELEASE


一、C位的AuthenticationManager

贯穿整个框架,我们随处可见各种Token,比如PreAuthenticatedAuthenticationToken、UsernamePasswordAuthenticationToken、RememberMeAuthenticationToken等等,顾名思义这就是框架运行过程中的各种凭证,而这些凭证其实都派生自Authentication,拿UsernamePasswordAuthenticationToken来举例。

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {...
}public abstract class AbstractAuthenticationToken implements Authentication,CredentialsContainer {...
}

就像有钥匙就必然有锁具一样,不然就无法达到“筛选”的目的了,同样的既然有凭证很自然的就凭证校验器,也就是当之无愧的C位AuthenticationManager。

public interface AuthenticationManager {Authentication authenticate(Authentication authentication)throws AuthenticationException;
}

这个接口看起来很简单,就提供一个authenticate方法,用于对凭证authentication的校验(好像的确也不需要其他方法了)。就一个地方需要提一下,就是入参和出参都是Authentication对象,咋一看好像不好理解,但稍微一想就好理解了。就好比你拿张机票去坐飞机,登机的时候工作人员会对你的机票进行检验,验票通过后会在上面打上标记然后再把票给你,也就说你提供了“票”,通过后还是得到了“票”,但两个“票”实际上已经是不同的了。
另外在框架中还提供了一个接口,叫AuthenticationProvider

public interface AuthenticationProvider {Authentication authenticate(Authentication authentication)throws AuthenticationException;boolean supports(Class<?> authentication);
}

可以看到该接口完全覆盖了AuthenticationManager的方法(这里没太理解为什么不直接继承,不符合设计规范吗?),实际上常用的业务实现类都是实现的AuthenticationProvider,比如我们常用的DaoAuthenticationProvider。
该接口的另外一个方法supports很明显就是判断是否支持该凭证的校验,比如公交卡要公交闸机校验,地铁卡则要地铁闸机校验。既然涉及到支不支持就说明校验器不止一个,也就需要一个统一的管理端进行调度,也就是ProviderManager

public class ProviderManager implements AuthenticationManager, MessageSourceAware,InitializingBean {...private List<AuthenticationProvider> providers = Collections.emptyList();...private AuthenticationManager parent;...public ProviderManager(List<AuthenticationProvider> providers,AuthenticationManager parent) {...this.providers = providers;this.parent = parent;...}...public Authentication authenticate(Authentication authentication)throws AuthenticationException {Class<? extends Authentication> toTest = authentication.getClass();...Authentication result = null;...for (AuthenticationProvider provider : getProviders()) {if (!provider.supports(toTest)) {continue;}...result = provider.authenticate(authentication);...}...if (result == null && parent != null) {...result = parent.authenticate(authentication);...}...if (result != null) {...return result;}...}...
}

不出所料他实现了AuthenticationManager的authenticate方法,然后遍历providers,判断是否支持相应的凭证,若支持就进行凭证校验,并且得到校验结果。有一点需要注意的是,在所有的provider均未支持该凭证时,尝试让父级进行校验,当然父级可能也是ProviderManager,所以就不断递归向上,直到把所有的校验器都找个遍或者直到找到匹配的校验器。这个过程类似于ClassLoader的loadClass方法使用的双亲委派机制,不同的是loadClass是优先父级方法调用,好处是防止被自定义的ClassLoader篡改,避免出现满屏的ClassCastException。
最后我们再来看看常用的DaoAuthenticationProvider是如何定义的

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {...
}public abstract class AbstractUserDetailsAuthenticationProvider implementsAuthenticationProvider, InitializingBean, MessageSourceAware {...public boolean supports(Class<?> authentication) {return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));}...}

看吧,DaoAuthenticationProvider正是支持UsernamePasswordAuthenticationToken,正所谓一个萝卜一个坑,他两就是互相配合工作的最佳拍档。
以上就是对SpringSecurity框架认证过程的简单拆解。

二、忠实的卫士AbstractSecurityInterceptor

认证通过成功后自然就是鉴权了,正如上文所说机票验证通过后,就需要对机票的等级进行验证了,经济舱的票肯定是无法在头等舱落座的(不考虑其他五花八门的因素)。
由从源码角度拆解SpringSecurity之勤劳的FilterChainProxy可知,框架会构建出一条Filter链,其中就有FilterSecurityInterceptor,前提是调用了类似“http.authorizeRequests()”的代码加入了ExpressionUrlAuthorizationConfigurer配置类。回忆下我们做安全配置的时候,不就是这么干的吗,比如促销活动接口无需鉴权,而订单列表接口则需要鉴权等等这样的需求。
那现在我们来看看构建出FilterSecurityInterceptor之后又是怎么来进行鉴权的呢

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implementsFilter {...public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {FilterInvocation fi = new FilterInvocation(request, response, chain);invoke(fi);}...public void invoke(FilterInvocation fi) throws IOException, ServletException {...InterceptorStatusToken token = super.beforeInvocation(fi);...fi.getChain().doFilter(fi.getRequest(), fi.getResponse());...super.afterInvocation(token, null);}...
}

不难看出,doFilter就表示通过拦截向我们的目标方法迈进一步,而beforeInvocation和afterInvocation就是我们的前置拦截器和后置拦截器(是不是感觉和AOP很像)

public abstract class AbstractSecurityInterceptor implements InitializingBean,ApplicationEventPublisherAware, MessageSourceAware {...private AccessDecisionManager accessDecisionManager;...protected InterceptorStatusToken beforeInvocation(Object object) {...Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);if (attributes == null || attributes.isEmpty()) {...return null; // no further work post-invocation}...Authentication authenticated = authenticateIfRequired();// Attempt authorizationtry {this.accessDecisionManager.decide(authenticated, object, attributes);}catch (AccessDeniedException accessDeniedException) {...throw accessDeniedException;}...}...
}

核心的鉴权过程实际上就交给AccessDecisionManager来进行的,AccessDecisionManager是一个接口,常用的实现类是AffirmativeBased

public class AffirmativeBased extends AbstractAccessDecisionManager {...public void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {int deny = 0;for (AccessDecisionVoter voter : getDecisionVoters()) {int result = voter.vote(authentication, object, configAttributes);...switch (result) {case AccessDecisionVoter.ACCESS_GRANTED:return;case AccessDecisionVoter.ACCESS_DENIED:deny++;break;default:break;}}if (deny > 0) {throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}...}
}

又是一个类似代理的模式,代理者会遍历内部维护的voters,然后依次执行各个voter的vote方法。比较有趣的是,这里的vote就没有像AuthenticationProvider那样进行suppor方法来判定是否支持,也没有像FilterChainProxy来严格控制Filter的顺序,なんですが?因为这里压根不需要这样的机制,可以看到若vote的结果是ACCESS_GRANTED则直接就通过了,反之若是ACCESS_DENIED先暂时将deny标记进行累加,一旦后续的voter得到了ACCESS_GRANTED也会直接通过,同样的如果是ACCESS_ABSTAIN则什么都不做继续执行下一个voter。所以结论就是只要有一个voter认为通过,则通过,反之在没有通过的前提下,只要有一个认为不通过,则不通过,另外不支持该ConfigAttribute还可以返回ACCESS_ABSTAIN来表示什么都不做。
以上是通过HttpSecurity进行的鉴权配置,而框架还支持了注解的方式来进行鉴权配置,就是我们熟悉的@PreAuthorize、@PostAuthorize,使用这些注解需要先开启配置,即@EnableGlobalMethodSecurity(prePostEnabled = true)

...
@Import({ GlobalMethodSecuritySelector.class })
...
@Configuration
public @interface EnableGlobalMethodSecurity {...boolean prePostEnabled() default false;...AdviceMode mode() default AdviceMode.PROXY;...
}final class GlobalMethodSecuritySelector implements ImportSelector {public final String[] selectImports(AnnotationMetadata importingClassMetadata) {...List<String> classNames = new ArrayList<>(4);if(isProxy) {classNames.add(MethodSecurityMetadataSourceAdvisorRegistrar.class.getName());}...if (!skipMethodSecurityConfiguration) {classNames.add(GlobalMethodSecurityConfiguration.class.getName());}...return classNames.toArray(new String[0]);}
}

使用这个注解后最终导入了MethodSecurityMetadataSourceAdvisorRegistrar和GlobalMethodSecurityConfiguration,而注解方式的权限配置主要就是通过这两进行注册的。

class MethodSecurityMetadataSourceAdvisorRegistrar implementsImportBeanDefinitionRegistrar {public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {BeanDefinitionBuilder advisor = BeanDefinitionBuilder.rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class);...advisor.addConstructorArgValue("methodSecurityInterceptor");...registry.registerBeanDefinition("metaDataSourceAdvisor",advisor.getBeanDefinition());}
}@Configuration
public class GlobalMethodSecurityConfigurationimplements ImportAware, SmartInitializingSingleton {...private MethodSecurityInterceptor methodSecurityInterceptor;@Beanpublic MethodInterceptor methodSecurityInterceptor() throws Exception {//isAspectJ方法就是取@EnableGlobalMethodSecurity的mode来判断 this.methodSecurityInterceptor = isAspectJ()? new AspectJMethodSecurityInterceptor(): new MethodSecurityInterceptor();methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());...methodSecurityInterceptor.setSecurityMetadataSource(methodSecurityMetadataSource());...return this.methodSecurityInterceptor;}...protected AccessDecisionManager accessDecisionManager() {List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>();...if (prePostEnabled()) {decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));}...return new AffirmativeBased(decisionVoters);}...@Beanpublic MethodSecurityMetadataSource methodSecurityMetadataSource() {List<MethodSecurityMetadataSource> sources = new ArrayList<>();...if (prePostEnabled()) {sources.add(new PrePostAnnotationSecurityMetadataSource(attributeFactory));}...return new DelegatingMethodSecurityMetadataSource(sources);}...
}

前面说什么来着,FilterSecurityInterceptor的invoke方法很像AOP是不是,这下《真》AOP来了。通过MethodSecurityMetadataSourceAdvisorRegistrar注册了MethodSecurityMetadataSourceAdvisor的BeanDefinition,同时将advice的beanName一同赋值进去,也就是GlobalMethodSecurityConfiguration中的methodSecurityInterceptor方法返回的bean。
可以看到实例化这个interceptor的时候,将鉴权需要的voter和source也都初始化进去了。但是发现没有,他们都做了一个判断,是否开启prePost设置,而这个值默认是false,所以要启用注解的鉴权配置需要将这个字段设置为true。
以上就是注解鉴权配置流程的简单拆解,注册好之后,运行时的维度和上一节的大体一样,就不再赘述了。

三、自定义数据权限鉴权框架

老规矩,既然熟悉了整个鉴权注册和运行过程,那是不是可以来搞一些定制化的需求呢,正好SpringSecurity原生是不支持数据权限的校验,需要在此基础上进行自定义开发,那么我们就针对这个小的需求点,看看如何实现的吧。
首先,数据权限是什么?咳咳,老实说我也没找到标准的定义,姑且认为是约束用户对数据的使用权力吧,通俗的讲就是用户要访问某个表的某行数据,需要判断是否有这行数据的访问权限、编辑权限、删除权限,或者对这个表中某个字段值的新增权限(可能不准确,望大佬指正)。举个简单例子就是用户要访问订单数据,就需要校验订单表中的用户ID是否和当前登录用户的ID一样,当然,实际使用还可能加入其他的校验方式,如是否是订单所属店铺的店主,是否是订单所在区域的管理员…完全可以根据业务灵活开发。
已经理解了数据权限的概念,接下来就简单说说如何实现。回顾上文,简单总结下权限校验的几个步骤:

  • 获取权限配置的数据源
  • 加入与之对应的voter
  • 切入接口进行鉴权操作

大体上看就如上3个步骤,那我们就一一进行实现吧。

获取权限配置和数据源

对于FilterSecurityInterceptor它的数据源是HttpSecurity的配置信息,对于MethodSecurityInterceptor则是对应的注解,我们先简单实现一种,即注解的方式即可。
那么首先得定义一个注解

/*** <p>* 设置数据权限的注解* </p>** @author hoe* @version 1.0* @date 2022/4/28 14:07*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataPermission {/*** 校验数据权限的执行方法名* @return 方法名*/String funcName() default "defaultDataPermission";/*** 校验数据权限的参数名<br/>* 支持SpEL表达式* @return 参数名数组*/String[] keys();/*** 数据权限校验模式* @return 默认为ANY模式*/Mode mode() default Mode.ANY;/*** 数据权限校验模式*/enum Mode {/*** 任意一个数据权限符合即通过*/ANY,/*** 所有数据权限均符合才通过*/ALL,/*** 所有数据权限都不符合才通过*/NONE}}

然后定义对应的source并在获取attribute时进行注解的解析

/*** <p>* 自定义数据权限数据源* </p>** @author hoe* @version 1.0* @date 2022/4/28 14:41*/
public class DataPermissionMetadataSource extends AbstractMethodSecurityMetadataSource {private final Object parserLock = new Object();private ExpressionParser parser;@Overridepublic Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {...DataPermission dataPermission = findAnnotation(method, targetClass, DataPermission.class);if (dataPermission == null) {// There is no meta-data so returnlogger.trace("No expression annotations found");return Collections.emptyList();}String[] keys = dataPermission.keys();Expression[] exps = null;if (keys.length > 0) {exps = new Expression[keys.length];int idx = 0;for (String key : keys) {exps[idx++] = getParser().parseExpression(key);}}return Arrays.asList(new DataPermissionInvocationAttribute(dataPermission.funcName(), exps, dataPermission.mode()));}...}

加入与之对应的voter

数据源解析完毕,接下来就需要自定义voter对解析的数据进行鉴权操作

/*** <p>* 自定义数据权限voter* </p>** @author hoe* @version 1.0* @date 2022/4/28 14:41*/
@Slf4j
public class DataPermissionVoter implements AccessDecisionVoter<MethodInvocation> {private final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();private final DataPermissionProperties properties;private final DataPermissionExecutor executor;private final Object lock = new Object();private Field subjectIdentityField = null;private Map<String, Method> exeMap = null;public DataPermissionVoter(DataPermissionProperties properties, DataPermissionExecutor executor) {this.properties = properties;this.executor = executor;}@Overridepublic boolean supports(ConfigAttribute attribute) {return attribute instanceof DataPermissionInvocationAttribute;}@Overridepublic boolean supports(Class<?> clazz) {return MethodInvocation.class.isAssignableFrom(clazz);}@Overridepublic int vote(Authentication authentication, MethodInvocation object, Collection<ConfigAttribute> attributes) {if (executor == null) {log.debug("未配置任何数据权限处理器,跳过校验");return ACCESS_ABSTAIN;}DataPermissionInvocationAttribute preAttr = findDataPermissionAttribute(attributes);if (preAttr == null) {return ACCESS_ABSTAIN;}Object principal = authentication.getPrincipal();if (principal == null) {log.info("用户未登录,直接认为数据权限校验不通过");return ACCESS_DENIED;}Serializable serializable = getSubjectIdentity(principal);EvaluationContext context = bindParam(object.getMethod(), object.getArguments());Expression[] exps = preAttr.getExps();Object[] args = new Object[exps.length];int idx = 0;for (Expression exp : exps) {args[idx++] = exp.getValue(context);}String error = invoke(preAttr.getFuncName(), serializable, args);return error == null || error.trim().length() == 0 ? ACCESS_GRANTED : ACCESS_DENIED;}...
}

逻辑很简单,一看就懂,就简单提几个点。如果未配置数据权限执行器,则认为不需要进行数据权限判断,直接返回ACCESS_ABSTAIN跳过投票。当然也可以实现为既然使用了@DataPermission注解就必须要配置数据权限执行器,这样的话就在这里抛出特定的自定义异常即可(需要异常解析)。另外就是如果没获取到登录信息则直接判定为校验不通过,反之就获取到用户的ID拿去做数据权限校验。至于具体如何做权限校验,需要定义一个鉴权接口,并定义一个默认的鉴权方法,开发者可以根据自身业务进行扩展。

/*** <p>* 数据权限校验执行者* </p>** @author hoe* @version 1.0* @date 2022/4/28*/
public interface DataPermissionExecutor {/*** 一般小型项目都只会有一个默认的数据权限校验场景<br/>* 例如业务所属校验,以实例来说明就是店主对店铺进行更新,就判断店铺是否属于该店主* @param subjectId 用户主键* @param keys 需要鉴权数据的ID* @param mode 鉴权模式* @return 异常信息,若为null表示鉴权通过*/String defaultDataPermission(Serializable subjectId, Object[] keys, DataPermission.Mode mode);}

有了这个执行器,开发者的各种ServiceImpl就可以实现该接口,进行实际执行数据权限的校验。拿本demo来举例就是这样实现的

public interface IXXXService extends IService<XXX>, DataPermissionExecutor {}public class XXXServiceImpl extends ServiceImpl<XXXMapper, XXX> implements IXXXService {@Overridepublic String defaultDataPermission(Serializable subjectId, Object[] keys, DataPermission.Mode mode) {log.info("defaultDataPermission subjectId:{} keys:{} mode:{}", subjectId, JSON.toJSONString(keys), mode);//TODO 根据主键ID查询具体表的数据权限//TODO 通过特定的方法已经知道具体要查询什么表的数据权限,故直接可以进行查询//TODO 查询到之后对用户的实际权限集合和要访问数据进行对比,再结合鉴权模式,最终得出鉴权结果//TODO 现只是一个简单demo,后期还可以扩展访问类型,如C、R、U、D,进一步对权限粒度进行划分return null;}
}

好了,至此已经完成鉴权相关组件的准备工作了,最后一步就是对鉴权接口,即对定义了@DataPermission注解的接口进行切入。

切入接口进行鉴权操作

通过上一节对MethodSecurityInterceptor注册过程的拆解,我们不难得到解决办法。思路就是切入MethodSecurityInterceptor的实例化方法,在返回时加入我们的source和voter,即可无缝注入到原生注解鉴权框架中。

@Aspect
@Configuration
@EnableConfigurationProperties(value = DataPermissionProperties.class)
public class DataPermissionConfig {static final String PREFIX = Const.PACKAGE + "data-permission";@Autowiredprivate DataPermissionProperties properties;@Autowired(required = false)private DataPermissionExecutor executor;@Pointcut("execution(* org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration.methodSecurityInterceptor(..))")public void interceptorPointcut() {}@Around("interceptorPointcut()")public Object interceptorAround(ProceedingJoinPoint joinPoint) throws Throwable {MethodSecurityInterceptor proceed = (MethodSecurityInterceptor) joinPoint.proceed();AffirmativeBased manager = (AffirmativeBased) proceed.getAccessDecisionManager();manager.getDecisionVoters().add(new DataPermissionVoter(properties, executor));DelegatingMethodSecurityMetadataSource source = (DelegatingMethodSecurityMetadataSource) proceed.getSecurityMetadataSource();source.getMethodSecurityMetadataSources().add(new DataPermissionMetadataSource());return proceed;}}

可以看到,由于定义了切点,所以需要精确到方法级别,随着后期版本迭代很可能方法不是这个了,就会导致兼容问题,所以后期对Spring版本进行升级时需要注意。
另外,在数据权限框架中还可以完善一些配置来更好的控制整个鉴权过程的顺利进行。

@Data
@ConfigurationProperties(prefix = DataPermissionConfig.PREFIX)
public class DataPermissionProperties {/*** 超管是否直接授权*/private Boolean isSuperAdminGrant = true;/*** 主体标识字段名*/private String subjectIdentityName = "userId";...
}

对应的配置文件

com.xx.xx:...#数据权限配置data-permission:isSuperAdminGrant: truesubjectIdentityName: userId

至此就完成自定义数据权限鉴权框架的开发。
PPPPPPPS:这只是一个Demo级实现,代码未进行设计和封装,还有很多属性、配置未考虑周详,如果要实现完备,还需从产品层面仔细设计。


简单总结本章内容,对SpringSecurity框架的认证和鉴权过程进行了拆解,了解了Authentication和AuthenticationManager是一对最佳拍档。鉴权操作分为HttpSecurity配置和接口注解配置两种,以及他们都是基于特定的voter进行实际的鉴权动作。最后还针对本章的学习借用一个简单的小需求——自定义数据权限鉴权框架进行实战
怎么样,你看懂了吗,有问题或者发现文章当中有谬误的地方,都欢迎留言评论哦。


至此我们已经完成了从源码角度拆解SpringSecurity系列文章的全部内容,感谢各位大佬能看到这里,谢谢支持。

转载请注明出处:从源码角度拆解SpringSecurity之C位的AuthenticationManager

从源码角度拆解SpringSecurity之C位的AuthenticationManager相关推荐

  1. 从源码角度解析线程池中顶层接口和抽象类

    摘要:我们就来看看线程池中那些非常重要的接口和抽象类,深度分析下线程池中是如何将抽象这一思想运用的淋漓尽致的. 本文分享自华为云社区<[高并发]深度解析线程池中那些重要的顶层接口和抽象类> ...

  2. Mybatis底层原理学习(二):从源码角度分析一次查询操作过程

    在阅读这篇文章之前,建议先阅读一下我之前写的两篇文章,对理解这篇文章很有帮助,特别是Mybatis新手: 写给mybatis小白的入门指南 mybatis底层原理学习(一):SqlSessionFac ...

  3. 从源码角度来读Handler

    最近打算从源码角度来读一下Handler,MessageQueue,Message,Looper,这四个面试必考项.所以今天先从Handler开始. 一.Handler的作用 源码定义 There a ...

  4. 从源码角度分析MapReduce的map-output流程

    文章目录 前言 流程图 源码分析 1 runNewMapper方法 2.NewOutputCollector方法 2.1 createSortingCollector方法 2.1.1 collecto ...

  5. 从源码角度入手实现RecyclerView的Item点击事件

    转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6758373.html RecyclerView 作为 ListView 和 GridView 的替代产物, ...

  6. 从JDK源码角度看Long

    概况 Java的Long类主要的作用就是对基本类型long进行封装,提供了一些处理long类型的方法,比如long到String类型的转换方法或String类型到long类型的转换方法,当然也包含与其 ...

  7. 【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  8. 从源码角度看Android系统Launcher在开机时的启动过程

    Launcher是Android所有应用的入口,用来显示系统中已经安装的应用程序图标. Launcher本身也是一个App,一个提供桌面显示的App,但它与普通App有如下不同: Launcher是所 ...

  9. 从源码角度看Android系统SystemServer进程启动过程

    SystemServer进程是由Zygote进程fork生成,进程名为system_server,主要用于创建系统服务. 备注:本文将结合Android8.0的源码看SystemServer进程的启动 ...

最新文章

  1. 更加安全的存取账户密码
  2. 美团智能问答技术探索与实践
  3. 干货丨机器学习和深度学习概念入门
  4. Python介绍以及Python 优缺点
  5. 两阶段提交协议的异常处理
  6. Python chardet 字符编码判断
  7. 关于.Net 1.1 Windows Forms 控件的一个小问题
  8. access里面的表达式运用_Access表达式解析
  9. 安卓手机阅读器_乐应用|安卓手机本地阅读的不二之选
  10. MPC运动学方法实现轨迹跟踪推导
  11. python错误提示库没有注册_SpringBoot实现登录注册常见问题解决方案
  12. mysql 子查询 in 多表_MySQL多表之子查询
  13. 鹏拍:软件行业上市十大关注问题
  14. CentOS7.0下安装PHP5.6.30服务
  15. [SPOJ CIRU]The area of the union of circles(自适应Simpson积分求圆并面积)
  16. 信息论实验-信源编码算法 (Huffman and Shannonn Fano编码C++实现)
  17. 理解Kademlia协议原理
  18. 来自Bitly的USA.gov数据,数据分析案例
  19. 传奇服是怎样架设的,怎样搭建一个属于自己的游戏服 10分钟学会游戏架设 玩转云服务器搭建游戏
  20. 2021年英语四级作文

热门文章

  1. 头条项目---01项目介绍和工程搭建
  2. 《每天五分钟冲击python基础之字符串练习题》(七)
  3. 特征选择:概述与方法
  4. android 多个fragment切换报错Caused by: java.lang.IllegalArgumentException: No view found for id 0x7f0800f
  5. 全球与中国通用级聚苯乙烯(GPPS)市场深度研究分析报告
  6. 关于虚拟机镜像无法检测
  7. SMB交换机、接入交换机、汇聚交换机、核心交换机
  8. 回车键换行符回车符 朦胧中!
  9. STM32外部Flash移植FATFS笔记
  10. 华为手机记事本导出_手机自带记事本如何备份