Shiro框架:Shiro用户访问控制鉴权流程-Aop注解方式源码解析

目录

1.Spring Aop嵌入点解析

2.Shiro框架Aop切面逻辑解析

2.1 通过注解实现切点

2.2 通过增强逻辑执行校验过程

2.2.1 增强实现类AopAllianceAnnotationsAuthorizingMethodInterceptor

2.2.1.1 类图解析

2.2.1.2 实现增强方法 

2.2.1.3 Shiro校验逻辑实现

2.2.1.3.1 获取Shiro自定义方法拦截器

2.2.1.3.1.1 Shiro自定义方法拦截器类图

2.2.1.3.1.2 Shiro自定义方法拦截器创建 

2.2.1.3.2 supports方法过滤方法拦截器

2.2.1.3.3 方法拦截器执行


Shiro作为一款比较流行的登录认证、访问控制安全框架,被广泛应用在程序员社区;Shiro登录验证、访问控制、Session管理等流程内部都是委托给SecurityManager安全管理器来完成的,SecurityManager安全管理器前面文章已经进行了详细解析,详见:Shiro框架:Shiro SecurityManager安全管理器解析;在此基础上,前面文章已对Shiro用户登录认证流程(详见:Shiro框架:Shiro登录认证流程源码解析进行了源码跟踪,Shiro框架:Shiro用户访问控制鉴权流程-内置过滤器方式源码解析 也对用户访问控制鉴权流程-内置过滤器方式进行了详细解析本篇文章继续对用户访问控制鉴权流程-Aop注解方式进行源码解析,了解不同的使用方式以便更好的应用到实际项目中;

想要深入了解Shiro框架整体原理,可移步:

Shiro框架:ShiroFilterFactoryBean过滤器源码解析、

Shiro框架:Shiro内置过滤器源码解析

Shiro的权限校验逻辑是嵌入到Spring Aop的增强逻辑中进行执行的,下面先看一下Shiro与Spring Aop是如何结合的,也即对Shiro校验逻辑在Spring Aop的嵌入点进行分析;

1.Spring Aop嵌入点解析

Shiro的校验逻辑应用到了Aop框架的抽象类StaticMethodMatcherPointcutAdvisor,该类的继承层次结构如下:

可以看出,其实现了切点Pointcut和切面Advisor 2个顶层接口,并通过实现方法匹配接口MethodMatcher定位切点,进而执行增强逻辑;

在Shiro框架中,实现StaticMethodMatcherPointcutAdvisor的具体子类为AopAllianceAnnotationsAuthorizingMethodInterceptor,其同时实现了Shiro框架的切点和增强逻辑,下面进行具体分析;

对Spring Aop的实现逻辑感兴趣,可以参考之前的文章:

Spring源码:Aop中@Aspect切面的解析代理过程

Spring源码:Aop源码分析

2.Shiro框架Aop切面逻辑解析

Shiro框架的Aop切面逻辑主要包含了2部分:

切点(Pointcut):Shiro如何通过自定义注解方式捕捉切点

增强逻辑(Advice):Shiro如何通过增强逻辑执行内部校验过程

Shiro的切点和增强逻辑定义在AuthorizationAttributeSourceAdvisor切面类中的,其类图如下:

可以看出,其继承了StaticMethodMatcherPointcutAdvisor类,并植入了切点和增强逻辑,下面分别进行解析;

2.1 通过注解实现切点

AuthorizationAttributeSourceAdvisor类中,是通过实现接口MethodMatcher的方法matches引入切点的,具体实现如下:

    public boolean matches(Method method, Class targetClass) {
        Method m = method;

        if ( isAuthzAnnotationPresent(m) ) {
            return true;
        }

        //The 'method' parameter could be from an interface that doesn't have the annotation.
        //Check to see if the implementation has it.
        if ( targetClass != null) {
            try {
                m = targetClass.getMethod(m.getName(), m.getParameterTypes());
                return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
            } catch (NoSuchMethodException ignored) {
                //default return value is false.  If we can't find the method, then obviously
                //there is no annotation, so just use the default return value.
            }
        }

        return false;
    }

    private boolean isAuthzAnnotationPresent(Class<?> targetClazz) {
        for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
            Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass);
            if ( a != null ) {
                return true;
            }
        }
        return false;
    }

    private boolean isAuthzAnnotationPresent(Method method) {
        for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
            Annotation a = AnnotationUtils.findAnnotation(method, annClass);
            if ( a != null ) {
                return true;
            }
        }
        return false;
    }

如上,通过判断Class级别和Method级别是否包含注解AUTHZ_ANNOTATION_CLASSES,来判断是否命中切点;

这里AUTHZ_ANNOTATION_CLASSES的定义如下:

    private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
            new Class[] {
                    RequiresPermissions.class, RequiresRoles.class,
                    RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
            };

这里的注解都是Shiro框架内置的自定义注解,比如RequiresPermissions注解用于用户权限校验,RequiresRoles注解用于用户角色校验,RequiresAuthentication注解校验用户登录状态;

因此Shiro通过自定义注解的方式实现了Aop切点;

2.2 通过增强逻辑执行校验过程

Shiro的增强逻辑同样在类AuthorizationAttributeSourceAdvisor中,在构造方法中通过setAdvice指定了增强实现类,如下:

    /**
     * Create a new AuthorizationAttributeSourceAdvisor.
     */
    public AuthorizationAttributeSourceAdvisor() {
        setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
    }

在类AopAllianceAnnotationsAuthorizingMethodInterceptor中,实现了是如何进行增强逻辑处理的,下面进行具体分析;

2.2.1 增强实现类AopAllianceAnnotationsAuthorizingMethodInterceptor

2.2.1.1 类图解析

AopAllianceAnnotationsAuthorizingMethodInterceptor类图如下,

左侧类层次结构是Shiro内部的实现,用于实现Shiro的增强逻辑

右侧类层次结构是Spring Aop中实现增强的切入点,这里实现了MethodInterceptor方法拦截器接口,其内部通过引入的invoke方式执行增强逻辑:

public interface MethodInterceptor extends Interceptor {
	
	/**
	 * Implement this method to perform extra treatments before and
	 * after the invocation. Polite implementations would certainly
	 * like to invoke {@link Joinpoint#proceed()}.
	 * @param invocation the method invocation joinpoint
	 * @return the result of the call to {@link Joinpoint#proceed()};
	 * might be intercepted by the interceptor
	 * @throws Throwable if the interceptors or the target object
	 * throws an exception
	 */
	Object invoke(MethodInvocation invocation) throws Throwable;

}

下面主要分析下AopAllianceAnnotationsAuthorizingMethodInterceptor是如何实现invoke方法的;

2.2.1.2 实现增强方法 

增强方法的实现如下:

    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
        return super.invoke(mi);
    }

这里创建了Shiro内部自定义的MethodInvocation,然后调用父类invoke方法,createMethodInvocation实现如下:

    protected org.apache.shiro.aop.MethodInvocation createMethodInvocation(Object implSpecificMethodInvocation) {
        final MethodInvocation mi = (MethodInvocation) implSpecificMethodInvocation;

        return new org.apache.shiro.aop.MethodInvocation() {
            public Method getMethod() {
                return mi.getMethod();
            }

            public Object[] getArguments() {
                return mi.getArguments();
            }

            public String toString() {
                return "Method invocation [" + mi.getMethod() + "]";
            }

            public Object proceed() throws Throwable {
                return mi.proceed();
            }

            public Object getThis() {
                return mi.getThis();
            }
        };
    }

父类AuthorizingMethodInterceptor的invoke方法实现如下:

public abstract class AuthorizingMethodInterceptor extends MethodInterceptorSupport {

    /**
     * Invokes the specified method (<code>methodInvocation.{@link org.apache.shiro.aop.MethodInvocation#proceed proceed}()</code>
     * if authorization is allowed by first
     * calling {@link #assertAuthorized(org.apache.shiro.aop.MethodInvocation) assertAuthorized}.
     */
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        assertAuthorized(methodInvocation);
        return methodInvocation.proceed();
    }

    /**
     * Asserts that the specified MethodInvocation is allowed to continue by performing any necessary authorization
     * (access control) checks first.
     * @param methodInvocation the <code>MethodInvocation</code> to invoke.
     * @throws AuthorizationException if the <code>methodInvocation</code> should not be allowed to continue/execute.
     */
    protected abstract void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException;

}

可以看到,invoke方法内,首先调用assertAuthorized方法执行增强逻辑(Shiro框架中即为自定义校验逻辑),校验通过,再继续执行目标方法,下面重点分析下assertAuthorized的具体实现逻辑;

2.2.1.3 Shiro校验逻辑实现

assertAuthorized方法的实现是在类AnnotationsAuthorizingMethodInterceptor中,如下:

    protected void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException {
        //default implementation just ensures no deny votes are cast:
        Collection<AuthorizingAnnotationMethodInterceptor> aamis = getMethodInterceptors();
        if (aamis != null && !aamis.isEmpty()) {
            for (AuthorizingAnnotationMethodInterceptor aami : aamis) {
                if (aami.supports(methodInvocation)) {
                    aami.assertAuthorized(methodInvocation);
                }
            }
        }
    }

如上,这里主要包含了3个部分:

  1. 首先获取Shiro自定义的方法拦截器
  2. 然后通过supports方法过滤方法拦截器
  3. 最后调用方法拦截器的assertAuthorized方法实现拦截

下面进行具体分析:

2.2.1.3.1 获取Shiro自定义方法拦截器

Shiro自定义方法拦截器保存在了成员变量methodInterceptors中,其具体赋值是在AopAllianceAnnotationsAuthorizingMethodInterceptor构造函数中,如下:

    public AopAllianceAnnotationsAuthorizingMethodInterceptor() {
        List<AuthorizingAnnotationMethodInterceptor> interceptors =
                new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);

        //use a Spring-specific Annotation resolver - Spring's AnnotationUtils is nicer than the
        //raw JDK resolution process.
        AnnotationResolver resolver = new SpringAnnotationResolver();
        //we can re-use the same resolver instance - it does not retain state:
        interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
        interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));
        interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
        interceptors.add(new UserAnnotationMethodInterceptor(resolver));
        interceptors.add(new GuestAnnotationMethodInterceptor(resolver));

        setMethodInterceptors(interceptors);
    }

下面首先对Shiro自定义方法拦截器进行分析;

2.2.1.3.1.1 Shiro自定义方法拦截器类图

如上,实例化了多个Shiro自定义方法拦截器,整体类图如下:

可以看出这里的PermissionAnnotationMethodInterceptor方法拦截器等也是实现了Shiro MethodInterceptor接口,并且通过组合的方式注入到了前面的增强类AopAllianceAnnotationsAuthorizingMethodInterceptor中;

2.2.1.3.1.2 Shiro自定义方法拦截器创建 

在上面Shiro多个自定义方法拦截器创建时,这里以PermissionAnnotationMethodInterceptor为例分析,其创建过程如下:

    public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver) {
        super( new PermissionAnnotationHandler(), resolver);
    }

这里绑定了注解处理器PermissionAnnotationHandler,注解处理器的整体类图图示如下:

 在PermissionAnnotationHandler内部通过构造函数绑定了注解@RequiresPermissions,如下:

public class PermissionAnnotationHandler extends AuthorizingAnnotationHandler {

    /**
     * Default no-argument constructor that ensures this handler looks for
     * {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions} annotations.
     */
    public PermissionAnnotationHandler() {
        super(RequiresPermissions.class);
    }

    //……
}

另外,我们可以看到实例化时注入了注解解析器,这里实际为SpringAnnotationResolver,主要完成对指定注解的解析工作,实现如下:

public class SpringAnnotationResolver implements AnnotationResolver {

    public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
        Method m = mi.getMethod();

        Annotation a = AnnotationUtils.findAnnotation(m, clazz);
        if (a != null) return a;

        //The MethodInvocation's method object could be a method defined in an interface.
        //However, if the annotation existed in the interface's implementation (and not
        //the interface itself), it won't be on the above method object.  Instead, we need to
        //acquire the method representation from the targetClass and check directly on the
        //implementation itself:
        Class<?> targetClass = mi.getThis().getClass();
        m = ClassUtils.getMostSpecificMethod(m, targetClass);
        a = AnnotationUtils.findAnnotation(m, clazz);
        if (a != null) return a;
        // See if the class has the same annotation
        return AnnotationUtils.findAnnotation(mi.getThis().getClass(), clazz);
    }
}

归纳总结,在PermissionAnnotationMethodInterceptor创建过程中,注入了PermissionAnnotationHandler和SpringAnnotationResolver:

  1. PermissionAnnotationHandler注解处理器:绑定了注解@RequiresPermissions
  2. SpringAnnotationResolver注解解析器:用于判断目标方法是否存在指定注解
2.2.1.3.2 supports方法过滤方法拦截器

supports方法的实现是在父类AnnotationMethodInterceptor中,如下:

    public boolean supports(MethodInvocation mi) {
        return getAnnotation(mi) != null;
    }

    protected Annotation getAnnotation(MethodInvocation mi) {
        return getResolver().getAnnotation(mi, getHandler().getAnnotationClass());
    }

这里通过getHandler().getAnnotationClass()方法获取绑定的注解,同样以PermissionAnnotationMethodInterceptor为例,getHandler()即为PermissionAnnotationHandler,getAnnotationClass()即为@RequiresPermissions;

这里getResolver()即为前面创建的SpringAnnotationResolver,通过getAnnotation方法获取目标方法上的@RequiresPermissions注解,并判断该注解是否存在,存在则匹配成功,然后继续调用方法拦截器进行执行;

2.2.1.3.3 方法拦截器执行

通过调用assertAuthorized方法执行拦截逻辑,以PermissionAnnotationMethodInterceptor为例,其实现是在父类AuthorizingAnnotationMethodInterceptor中实现的,如下:

    public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
        try {
            ((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi));
        }
        catch(AuthorizationException ae) {
            // Annotation handler doesn't know why it was called, so add the information here if possible. 
            // Don't wrap the exception here since we don't want to mask the specific exception, such as 
            // UnauthenticatedException etc. 
            if (ae.getCause() == null) ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod()));
            throw ae;
        }         
    }

这里又委托给注解处理器,即PermissionAnnotationHandler,调用其方法assertAuthorized执行校验逻辑,参数为匹配到的目标方法上的注解@RequiresPermissions,具体实现如下:

    public void assertAuthorized(Annotation a) throws AuthorizationException {
        if (!(a instanceof RequiresPermissions)) return;

        RequiresPermissions rpAnnotation = (RequiresPermissions) a;
        String[] perms = getAnnotationValue(a);
        Subject subject = getSubject();

        if (perms.length == 1) {
            subject.checkPermission(perms[0]);
            return;
        }
        if (Logical.AND.equals(rpAnnotation.logical())) {
            getSubject().checkPermissions(perms);
            return;
        }
        if (Logical.OR.equals(rpAnnotation.logical())) {
            // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
            boolean hasAtLeastOnePermission = false;
            for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
            // Cause the exception if none of the role match, note that the exception message will be a bit misleading
            if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
            
        }
    }

这里获取到了注解上配置的访问目标方法所需要的权限,也获取了当前登录用户Subject,然后调用其checkPermission方法执行权限校验,实现如下:

    public void checkPermission(String permission) throws AuthorizationException {
        assertAuthzCheckPossible();
        securityManager.checkPermission(getPrincipals(), permission);
    }

可以看到,这里也是委托给了安全管理器执行权限校验,并获取了当前登录用户的账户名Principals,进一步跟踪如下:

    public void checkPermission(PrincipalCollection principals, String permission) throws AuthorizationException {
        this.authorizer.checkPermission(principals, permission);
    }

这里继续委托给权限校验器authorizer执行权限校验,这里具体类为ModularRealmAuthorizer,继续跟踪如下:

    /**
     * If !{@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)}, throws
     * an <code>UnauthorizedException</code> otherwise returns quietly.
     */
    public void checkPermission(PrincipalCollection principals, Permission permission) throws AuthorizationException {
        assertRealmsConfigured();
        if (!isPermitted(principals, permission)) {
            throw new UnauthorizedException("Subject does not have permission [" + permission + "]");
        }
    }

如上会校验权限组件Realm已配置,并委托给isPermitted方法校验当前用户是否具备指定权限,没有配置指定权限时,会抛出权限校验异常UnauthorizedException;

后续isPermitted方法的处理流程可以参见的Shiro框架:Shiro用户访问控制鉴权流程-内置过滤器方式源码解析 的章节“3.2.1 isPermitted权限校验”。

至此,Shiro用户访问控制鉴权流程-Aop注解方式源码解析完成,Over~~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JVM篇--垃圾回收器高频面试题

1 你知道哪几种垃圾收集器&#xff0c;各自的优缺点是啥&#xff0c;重点讲下cms和G1&#xff0c;包括原理&#xff0c;流程&#xff0c;优缺点&#xff1f; 1&#xff09;首先简单介绍下 有以下这些垃圾回收器 Serial收集器&#xff1a; 单线程的收集器&#xff0c;收集垃圾时…

Flink(十四)【Flink SQL(中)查询】

前言 接着上次写剩下的查询继续学习。 Flink SQL 查询 环境准备&#xff1a; # 1. 先启动 hadoop myhadoop start # 2. 不需要启动 flink 只启动yarn-session即可 /opt/module/flink-1.17.0/bin/yarn-session.sh -d # 3. 启动 flink sql 的环境 sql-client ./sql-client.sh …

Tomcat Notes: Web Security

This is a personal study notes of Apache Tomcat. Below are main reference material. - YouTube Apache Tomcat Full Tutorial&#xff0c;owed by Alpha Brains Courses. https://www.youtube.com/watch?vrElJIPRw5iM&t801s 1、Overview2、Two Levels Of Web Securi…

深入Matplotlib:画布分区与高级图形展示【第33篇—python:Matplotlib】

文章目录 Matplotlib画布分区技术详解引言方法一&#xff1a;plt.subplot()方法二&#xff1a;简略写法方法三&#xff1a;plt.subplots()实例展示添加更多元素 进一步探索Matplotlib画布分区自定义子图布局3D子图结语 Matplotlib画布分区技术详解 引言 Matplotlib是一个强大…

1.6万字全面掌握 BERT:自然语言处理(NLP)从初学到高级的全面指南

BERT&#xff08;双向编码器表示来自Transformer的模型&#xff09;是由Google开发的一种革命性的自然语言处理&#xff08;NLP&#xff09;模型。它改变了语言理解任务的格局&#xff0c;使机器能够理解语言中的上下文和细微差异。 在本博客中&#xff0c;我们将带您从 BERT …

动态路由协议 - OSPF 基本配置 详解 (反掩码,宣告,三张表,Cost默认值修改 )

目录 预备工作 &#xff1a; 基础配置 &#xff1a; 先启动 OSPF 的进程 &#xff1a; 创建区域 &#xff1a; 宣告 &#xff1a; 查看三张表 邻居表 &#xff1a; 数据库表 &#xff1a; 路由表 &#xff1a; 以下示拓扑为 OSPF 示范 &#xff1a; 第一步…

基于python卷积网络对漫画人物好坏识别-含数据集和代码

数据集介绍&#xff0c;下载本资源后&#xff0c;界面如下&#xff1a; 有一个文件夹一个是存放数据集的文件。 数据集介绍&#xff1a; 一共含有:2个类别&#xff0c;包含:evil, good等。 然后本地的train.txt和val.txt里面存放的是数据集的图片路径和对应的标签。 运行trai…

linux驱动(八):block,net

本文主要探讨210的block驱动和net驱动。 block 随机存取设备且读写是按块进行,缓冲区用于暂存数据,达条件后一次性写入设备或读到缓冲区 块设备与字符设备:同一设备支持块和字符访问策略,块设备驱动层支持缓冲区,字符设备驱动层没有缓冲 块设备单位:扇…

基于python深度学习的颜色识别-含数据集和代码

数据集介绍&#xff0c;下载本资源后&#xff0c;界面如下&#xff1a; 有一个文件夹一个是存放数据集的文件。 数据集介绍&#xff1a; 一共含有:10个类别&#xff0c;包含:black, blue, brown, green, grey, orange, red, violet, white, yellow等。 然后本地的train.txt和…

什么是游戏盾?哪家效果好。

游戏盾是什么呢&#xff0c;很多做游戏开发的客户估计都是听说过的&#xff0c;但是也不是所有的游戏开发者会运用到。因为&#xff0c;游戏盾是针对游戏行业APP业务所推出的高度可定制的网络安全管理解决方案&#xff0c;除了能针对大型DDoS攻击(T级别)进行有效防御外&#xf…

腾讯云com域名注册怎么收费?

腾讯云com域名首年价格&#xff0c;企业新用户注册com域名首年1元&#xff0c;个人新用户注册com域名33元首年&#xff0c;非新用户注册com域名首年元85元一年&#xff0c;优惠价75元一年&#xff0c;com域名续费85元一年。腾讯云百科txybk.com分享腾讯云com域名注册优惠价格&a…

3.postman动态参数、文件上传及断言

一、postman内置动态参数以及自定义的动态参数 postman内置动态参数&#xff1a; {{$timestamp}} 生成当前时间的时间戳 {{$randomint}} 生成0-1000之间的随机数 {{$guid}} 生成随机guid字符串 自定义动态参数&#xff1a; 在请求中pre-req页面下 //手动的获得时间戳 var…

关于索引的最常见的十道面试题

面试题一&#xff1a;索引底层如何实现的&#xff1f; MySQL索引的底层实现是取决于存储引擎的&#xff0c;但是是大部分存储引擎底层都是通过B树实现的&#xff0c;以默认的存储InnoDB为例&#xff0c;底层就是通过B树实现的&#xff0c;如下图所示&#xff1a; B树是一种自平…

NumPy2要来了,但先别急!

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 如果你正在使用 Python 编写代码&#xff0c;那么很有可能正在直接或间接地使用 NumPy 如Pandas、Scikit-Image、SciPy、Scikit-Learn、AstroPy…这些都依赖于 NumPy NumPy 2 是一个新的重要版本&am…

网络逻辑示意图工具

现代网络容纳了来自不同供应商的大量设备&#xff0c;支持一系列新技术&#xff0c;并跨越了分布在多个位置的边界&#xff0c;随着网络变得越来越复杂&#xff0c;网络管理员发现越来越难以跟踪网络领域的所有当代进步和发展&#xff0c;这使得网络管理比以往任何时候都更具挑…

Java8的Stream最佳实践

从这一篇文章开始&#xff0c;我们会由浅入深&#xff0c;全面的学习stream API的最佳实践&#xff08;结合我的使用经验&#xff09;&#xff0c;本想一篇写完&#xff0c;但写着写着发现需要写的内容太多了&#xff0c;所以分成一个系列慢慢来说。给大家分享我的经验的同时&a…

hadoop必记知识点(1)

1.Hadoop是什么&#xff0c;解决什么问题&#xff1f; Hadoop是一个由Apache基金会所开发的分布式系统基础架构。它可以让使用者在普通的硬件上搭建起一个强大的计算集群。Hadoop的特点包括&#xff1a;高可靠性、高扩展性、高容错性、支持大数据和高并发等。Hadoop核心组件包…

python写完程序怎么运行

python有两种运行方式&#xff0c;一种是在python交互式命令行下运行; 另一种是使用文本编辑器直接在命令行上运行。 注&#xff1a;以上两种运行方式均由CPython解释器编译运行。 当然&#xff0c;也可以将python代码写入eclipse中&#xff0c;用JPython解释器运行&#xff0c…

推荐系统|2.4 矩阵分解的目的和效果

文章目录 矩阵分解矩阵分解的必要性和方法隐向量 矩阵分解 矩阵分解的必要性和方法 比如原本是一个 m n m\times n mn规模大小的矩阵,经过分解后可得到两个矩阵一个是 m k m\times k mk&#xff0c;另外一个是 k n k\times n kn,于是总占用空间为 ( m n ) k (mn)\times k…

腾讯云.com域名报价

腾讯云com域名首年价格&#xff0c;企业新用户注册com域名首年1元&#xff0c;个人新用户注册com域名33元首年&#xff0c;非新用户注册com域名首年元85元一年&#xff0c;优惠价75元一年&#xff0c;com域名续费85元一年。腾讯云百科txybk.com分享腾讯云com域名注册优惠价格&a…