Shiro 安全框架详解二

总结内容

一、登录认证

Shiro 入门以及登录认证案例及实现请看我上篇博客:
Shiro 安全框架详解一(概念+登录案例实现)

二、Shiro 授权

1. 概念

系统中的授权功能就是为用户分配相关的权限,只有当用户拥有相应的权限后,才能访问对应的资源。

2. 授权流程图

三、基于 ini 的授权认证案例实现

1. 实现原理图

2. 实现代码

2.1 添加 maven jar包依赖

  1. <dependency>
  2. <groupId>commons-logging</groupId>
  3. <artifactId>commons-logging</artifactId>
  4. <version>1.1.3</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.shiro</groupId>
  8. <artifactId>shiro-core</artifactId>
  9. <version>1.5.2</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>junit</groupId>
  13. <artifactId>junit</artifactId>
  14. <version>4.13.2</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.projectlombok</groupId>
  18. <artifactId>lombok</artifactId>
  19. <version>1.16.22</version>
  20. <scope>provided</scope>
  21. </dependency>

2.2 编写 ini 配置文件:shiro-authc.ini

shiro默认支持的是ini配置的方式,这里只是为了方便,项目中还是会选择xml

  1. #用户的身份、凭据、角色
  2. [users]
  3. zhangsan=555,hr,seller
  4. admin=123,seller
  5. #角色与权限信息
  6. [roles]
  7. hr=employee:list,employee:delete
  8. seller=customer:list,customer:save
  • 权限表达式
    权限表达式的作用主要是用来在权限校验的时候使用,表达式中包含有当前访问资源的相关信息,应该具有唯一性,跟以前的权限表达式一致,shiro中也可以使用*通配符。

2.3 Shiro 常用 API

  • 常用API
    【1】判断用户拥有单个角色:用户对象.hasRole;
    【2】判断同时拥有多个角色:hasRoles;
    【3】判断拥有指定的角色:hasAllRoles ,全部拥有返回 true ,否则返回 false;
    【4】判断用户是否拥有某个权限:isPermitted(),有返回 true,否则返回 false;
    【5】ckeck 开头的是没有返回值的,当没有权限时就会抛出异常;
  1. @Test
  2. public void testAuthor(){
  3. //创建Shiro的安全管理器,是shiro的核心
  4. DefaultSecurityManager securityManager = new DefaultSecurityManager();
  5. //加载shiro.ini配置,得到配置中的用户信息(账号+密码)
  6. IniRealm iniRealm = new IniRealm("classpath:shiro-author.ini");
  7. securityManager.setRealm(iniRealm);
  8. //把安全管理器注入到当前的环境中
  9. SecurityUtils.setSecurityManager(securityManager);
  10. //无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
  11. Subject subject = SecurityUtils.getSubject();
  12. System.out.println("认证状态:"+subject.isAuthenticated());
  13. //创建令牌(携带登录用户的账号和密码)
  14. UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
  15. //执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
  16. subject.login(token);
  17. System.out.println("认证状态:"+subject.isAuthenticated());
  18. //登出
  19. //subject.logout();
  20. //System.out.println("认证状态:"+subject.isAuthenticated());
  21. //判断用户是否有某个角色
  22. System.out.println("role1:"+subject.hasRole("role1"));
  23. System.out.println("role2:"+subject.hasRole("role2"));
  24. //是否同时拥有多个角色
  25. System.out.println("是否同时拥有role1和role2:"+subject.hasAllRoles(Arrays.asList("role1", "role2")));
  26. //check开头的是没有返回值的,当没有权限时就会抛出异常
  27. subject.checkRole("hr");
  28. //判断用户是否有某个权限
  29. System.out.println("user:delete:"+subject.isPermitted("user:delete"));
  30. subject.checkPermission("user:delete");
  31. }

四、CRM 中集成 Shiro 授权

1. Shiro 权限验证三种方式

  1. 编程式 通过写 if/else 授权代码块完成
  1. Subject subject = SecurityUtils.getSubject();
  2. if(subject.hasRole("hr")) {
  3. //有权限
  4. } else {
  5. //无权限
  6. }
  1. 注解式 通过在controller的方法上放置相应的注解完成
  1. @RequiresRoles("admin")
  2. @RequiresPermissions("user:create")
  3. public void hello() {
  4. //有权限
  5. }
  1. JSP 标签(shiro 自带) 或 freemarker 的标签(第三方) 在页面通过相应的标签完成
  1. <@shiro.hasPermission name="employee:list">
  2. <!-- 有权限 -->
  3. </@shiro.hasRole>

五、项目中集成 Shiro 认证授权案例实现

1. 项目准备

项目准备请看我上一篇博客:
Shiro 安全框架详解一(概念+登录案例实现)

2. 代码实现

2.1 登录的 EmployeeController 控制器

  1. @RequiresPermissions(value = "employee:list")
  2. @RequestMapping("/list")
  3. public String list(Model model){
  4. model.addAttribute("depts", departmentService.list());
  5. return "employee/list";
  6. }
  • @RequiresPermissions(“employee:list”)
    权限限定注解,表示当前用户拥有employee:list 权限才可以访问当前请求映射方法
  • @RequiresRoles(“hr”)
    角色限定注解,表示当前用户拥有 hr 角色才可以访问当前请求映射方法

2.2 配置自定义Realm:CarBusinessRealm

  1. package cn.wolfcode.shiro;
  2. import cn.wolfcode.domain.Employee;
  3. import cn.wolfcode.domain.Role;
  4. import cn.wolfcode.service.IEmployeeService;
  5. import cn.wolfcode.service.IPermissionService;
  6. import cn.wolfcode.service.IRoleService;
  7. import org.apache.shiro.authc.AuthenticationException;
  8. import org.apache.shiro.authc.AuthenticationInfo;
  9. import org.apache.shiro.authc.AuthenticationToken;
  10. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  11. import org.apache.shiro.authz.AuthorizationInfo;
  12. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  13. import org.apache.shiro.realm.AuthorizingRealm;
  14. import org.apache.shiro.subject.PrincipalCollection;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import java.util.List;
  17. public class CarBusinessRealm extends AuthorizingRealm {
  18. @Autowired
  19. private IEmployeeService employeeService;
  20. @Autowired
  21. private IPermissionService permissionService;
  22. @Autowired
  23. private IRoleService roleService;
  24. @Override
  25. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  26. // 1、获取页面传入的账户:username
  27. String username = (String) token.getPrincipal();
  28. // 2、以账户为条件,查询用户对象
  29. Employee employee = employeeService.selectByUsername(username);
  30. if (employee == null) {
  31. return null;
  32. }
  33. // 封装成 info 数据
  34. return new SimpleAuthenticationInfo(employee, employee.getPassword(), this.getName());
  35. }
  36. @Override
  37. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  38. // 1、获取当前登录用户对象
  39. Employee employee = (Employee) principals.getPrimaryPrincipal();
  40. // 2、查询该用户对象的权限集合
  41. List<String> permissions = permissionService.selectByEmpId(employee.getId());
  42. // 3、查询该用户对象的角色集合
  43. List<String> roles = roleService.queryByEmpId(employee.getId());
  44. // 4、封装授权 info 对象
  45. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  46. // 是超管获取所有权限
  47. if (employee.isAdmin()) {
  48. List<Role> rolesT = roleService.listAll();
  49. for (Role role : rolesT) {
  50. info.addRole(role.getSn());
  51. }
  52. // 拥有所有权限
  53. info.addStringPermission("*:*");
  54. return info;
  55. }
  56. info.addRoles(roles);
  57. info.addStringPermissions(permissions);
  58. return info;
  59. }
  60. }

2.3 创建 shiro.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/aop
  8. http://www.springframework.org/schema/aop/spring-aop.xsd">
  9. <!-- <aop:config/> 会扫描配置文件中的所有 advisor,并为其创建代理 -->
  10. <aop:config/>
  11. <!-- Pointcut advisor 通知器,会匹配所有加了 shiro 权限注解的方法 -->
  12. <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
  13. <property name="securityManager" ref="securityManager"/>
  14. </bean>
  15. <!-- 自定义 realm -->
  16. <bean id="carBusinessRealm" class="cn.wolfcode.shiro.CarBusinessRealm"/>
  17. <!-- 安全管理器 -->
  18. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  19. <!-- 设置 realm 值-->
  20. <property name="realm" ref="carBusinessRealm"/>
  21. <!-- 注册缓存管理器 -->
  22. <property name="cacheManager" ref="cacheManager"/>
  23. </bean>
  24. <!-- 缓存管理器 -->
  25. <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
  26. <!-- 设置配置文件 -->
  27. <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
  28. </bean>
  29. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
  30. <!-- 引用指定的安全管理器 -->
  31. <property name="securityManager" ref="securityManager"/>
  32. <!-- shiro 默认的登录地址是 /login.jsp 现在要指定我们自己的登录页面地址 -->
  33. <property name="loginUrl" value="/login.html"/>
  34. <!-- 路径对应的规则:登录校验规则 -->
  35. <property name="filterChainDefinitions">
  36. <value>
  37. /userLogin=anon
  38. /appointment/save=anon
  39. /index=anon
  40. /css/**=anon
  41. /js/**=anon
  42. /img/**=anon
  43. /upload/**=anon
  44. /static/**=anon
  45. /userLogout=logout
  46. /**=authc
  47. </value>
  48. </property>
  49. </bean>
  50. </beans>

2.4 在SpringMVC 中引入 shiro.xml

  1. <import resource="classpath:shiro.xml"/>

2.5 安全管理器

在 JavaEE 环境中,我们需要使用的安全管理器是:DefaultWebSecurityManager

  1. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  2. <!-- 这里面有很多配置文件 -->
  3. </bean>

有了上面的配置,当我们的访问到达具体资源之前,会先进过指定的过滤器做预处理,在允许通过之后才能继续访问。

2.6 没有权限的异常处理

如果用户不是超级管理员,只能访问分配给他的相关资源,如果访问了没有权限的资源,会抛出下面的异常:

  1. **org.apache.shiro.authz.UnauthorizedException**: Subject does not have permission [department:list]

我们只需要想办法捕获到该异常,然后进行处理即可。

  1. @ExceptionHandler(AuthorizationException.class)
  2. public String exceptionHandler(AuthorizationException e, HandlerMethod method, HttpServletResponse response){
  3. e.printStackTrace(); //方便开发的时候找bug
  4. //如果原本控制器的方法是返回jsonresult数据,现在出异常也应该返回jsonresult
  5. //获取当前出现异常的方法,判断是否有ResponseBody注解,有就代表需要返回jsonresult
  6. if(method.hasMethodAnnotation(ResponseBody.class)){
  7. try {
  8. response.setContentType("application/json;charset=UTF-8");
  9. response.getWriter().print(JSON.toJSONString(new JsonResult("没有权限操作!")));
  10. } catch (IOException e1) {
  11. e1.printStackTrace();
  12. }
  13. return null;
  14. }
  15. //如果原本控制器的方法是返回视图页面,现在也应该返回视图页面
  16. return "common/nopermission";
  17. }

2.7 标签式权限验证

在前端页面上,我们通常可以根据用户拥有的权限来显示具体的页面,如:用户拥有删除员工的权限,页面上就把删除按钮显示出来,否则就不显示删除按钮,通过这种方式来细化权限控制。

要能够实现上面的控制,需要使用 Shiro 中提供的相关标签,标签的使用步骤如下:

  1. 拓展freemarker标签

前端页面我们选择的是freemarker,而默认 freemarker 是不支持 shiro 标签的,所以需要对其功能做拓展,可以理解为注册 shiro 的标签,达到在freemarker 页面中使用的目的

  1. public class ShiroFreeMarkerConfig extends FreeMarkerConfigurer {
  2. @Override
  3. public void afterPropertiesSet() throws IOException, TemplateException {
  4. //继承之前的属性配置,这不能省
  5. super.afterPropertiesSet();
  6. Configuration cfg = this.getConfiguration();
  7. cfg.setSharedVariable("shiro", new ShiroTags());//注册shiro 标签
  8. }
  9. }
  1. 在mvc.xml 中把以前的FreeMarkerConfigurer修改成我们自定义的MyFreeMarkerConfig类
  1. <bean class="cn.wolfcode.crm.util.ShiroFreeMarkerConfig">
  2. <!-- 配置 freemarker 的文件编码 -->
  3. <!-- ..略... -->
  4. </bean>

有了上面的准备工作后,我们就可以在freemarker 页面中使用 shiro 相关的标签来对页面显示做控制了。

  1. 使用shiro标签

常用标签:

authenticated 标签:已认证通过的用户。

  1. <@shiro.authenticated> </@shiro.authenticated>

notAuthenticated 标签:未认证通过的用户。与 authenticated 标签相对。

  1. <@shiro.notAuthenticated></@shiro.notAuthenticated>

principal 标签:输出当前用户信息,通常为登录帐号信息
后台是直接将整个员工对象作为身份信息的,所以这里可以直接访问他的 name 属性得到员工的姓名

  1. <@shiro.principal property="name" />

对应realm中返回的SimpleAuthenticationInfo对象的第一个参数

  1. new SimpleAuthenticationInfo(employee,employee.getPassword(),this.getName());

hasRole 标签:验证当前用户是否拥有该角色

  1. <@shiro.hasRole name="admin">Hello admin!</@shiro.hasRole>

hasAnyRoles 标签:验证当前用户是否拥有这些角色中的任何一个,角色之间逗号分隔

  1. <@shiro.hasAnyRoles name="admin,user,operator">Hello admin</@shiro.hasAnyRoles>

hasPermission 标签:验证当前用户是否拥有该权限

  1. <@shiro.hasPermission name="department:delete">删除</@shiro.hasPermission>

2.8 编程式权限验证

随便找个能执行到的地方,测试shiro提供的权限api是否能结合realm完成权限判断功能

  1. @RequestMapping("/list")
  2. public String list(Model model, QueryObject qo){
  3. System.out.println("当前用户是否有admin角色:"
  4. + SecurityUtils.getSubject().hasRole("admin"));
  5. System.out.println("当前登录用户是否有employee:delete权限:"
  6. + SecurityUtils.getSubject().isPermitted("employee:delete"));
  7. model.addAttribute("pageInfo", departmentService.query(qo));
  8. return "department/list";
  9. }

六、注销功能

shiro.xml 中的路径规则加入 /logout=logout 即可交给shiro来处理,我们以前写的LoginController中的logout方法可以删掉啦。

七、Shiro加密

加密的目的是从系统数据的安全考虑,如,用户的密码,如果我们不对其加密,那么所有用户的密码在数据库中都是明文,只要有权限查看数据库的都能够得知用户的密码,这是非常不安全的。所以,只要密码被写入磁盘,任何时候都不允许是明文, 以及对用户来说非常机密的数据,我们都应该想到使用加密技术,这里我们采用的是 MD5 加密。

如何实现项目中密码加密的功能:

  1. 添加用户的时候,对用户的密码进行加密

  2. 登录时,按照相同的算法对表单提交的密码进行加密然后再和数据库中的加密过的数据进行匹配

1. Shiro加密工具

在 Shiro 中实现了 MD5 的算法,所以可以直接使用它来对密码进行加密。

  1. @Test
  2. public void testMD5() throws Exception{
  3. Md5Hash hash = new Md5Hash("1");
  4. System.out.println(hash);//c4ca4238a0b923820dcc509a6f75849b
  5. }

MD5 加密的数据如果一样,那么无论在什么时候加密的结果都是一样的,所以,相对来说还是不够安全,但是我们可以对数据加“盐”。同样的数据加不同的“盐”之后就是千变万化的,因为我们不同的人加的“盐”都不一样。这样得到的结果相同率也就变低了。

盐一般要求是固定长度的字符串,且每个用户的盐不同。

可以选择用户的唯一的数据来作为盐(账号名,身份证等等),注意使用这些数据作为盐要求是不能改变的,假如登录账号名改变了,则再次加密时结果就对应不上了。

  1. @Test
  2. public void testMD5() throws Exception{
  3. Md5Hash hash = new Md5Hash("1","admin");
  4. System.out.println(hash);//e00cf25ad42683b3df678c61f42c6bda
  5. }

或者是在数据库中多记录一个数据 , 如下所述:

  1. username | password |salt
  2. ---------|---------—------------------------|----------
  3. zp1996 |2636fd878959548z2abf3423833901f6e |63UrCwJhTH
  4. zpy |659ec972c3ed72d04fac7a2147b5827b |84GljVnhDT

Md5Hash() 构造方法中的第二个参数就是对加密数据添加的“盐”,加密之后的结果也和之前不一样了。
如果还觉得不够安全,我们还可以通过加密次数来增加 MD5 加密的安全性。

  1. @Test
  2. public void testMD5() throws Exception{
  3. Md5Hash hash = new Md5Hash("1","admin",3);
  4. System.out.println(hash);//f3559efea469bd6de83d27d4284b4a7a
  5. }

上面指定对密码进行 3 次 MD5 加密,在开发中可以根据实际情况来选择。

2. 实现密码加密

在知道 Shiro 如何使用MD5 加密之后,接下来我们来看看如何将其使用到我们的 CRM 项目中来

2.1 步骤

  1. 在添加用户的时候,需要对用户的密码进行加密
  1. @Override
  2. public void save(Employee employee, Long[] ids) {
  3. //对密码进行加密(把用户名当做盐)
  4. Md5Hash md5Hash = new Md5Hash(employee.getPassword(), employee.getName());
  5. employee.setPassword(md5Hash.toString());
  6. //保存到数据库
  7. employeeMapper.insert(employee);
  8. //......
  9. }
  1. 在登录时, 先对前端传过来的密码进行相同算法的加密,再传给shiro进行认证处理
  1. @RequestMapping("/login")
  2. @ResponseBody
  3. public JsonResult login(String username, String password) {
  4. try {
  5. //对密码进行加密(把用户名当做盐)
  6. Md5Hash md5Hash = new Md5Hash(password, username);
  7. UsernamePasswordToken token = new UsernamePasswordToken(username, md5Hash.toString());
  8. SecurityUtils.getSubject().login(token);
  9. return new JsonResult();
  10. } catch (UnknownAccountException e) {
  11. return new JsonResult(false, "账号不存在");
  12. } .....
  13. }
  1. 测试效果之前先把数据库中的密码改为加密后的数据,有了以上操作之后,密码加密的功能就已经实现好了。

八、Shiro缓存

在请求中一旦需要进行权限的控制,如:

  1. @RequiresPermissions("employee:view") //注解
  1. <shiro:hasPermission name="employee:input"> //标签
  1. subject.hasRole("admin") //注解

都会去调用 Realm 中的 doGetAuthorizationInfo 方法获取用户的权限信息,这个授权信息是要从数据库中查询的, 如果每次授权都要去查询数据库就太频繁了,性能不好, 而且用户登陆后,授权信息一般很少变动,所以我们可以在第一次授权后就把这些授权信息存到缓存中,下一次就直接从缓存中获取,避免频繁访问数据库。

Shiro 中没有实现自己的缓存机制,只提供了一个可以支持具体缓存实现(如:Hazelcast, Ehcache, OSCache, Terracotta, Coherence, GigaSpaces, JBossCache 等)的抽象 API 接口,这样就允许 Shiro 用户根据自己的需求灵活地选择具体的 CacheManager。这里我们选择使用 EhCache。

1. 集成EhCache

  1. 配置缓存管理器并引用缓存管理器
  1. <!--安全管理器-->
  2. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  3. <!--注册自定义数据源-->
  4. <property name="realm" ref="employeeRealm"/>
  5. <!--注册缓存管理器-->
  6. <property name="cacheManager" ref="cacheManager"/>
  7. </bean>
  8. <!-- 缓存管理器 -->
  9. <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
  10. <!-- 设置配置文件 -->
  11. <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
  12. </bean>
  1. 添加缓存配置文件

shiro-ehcache.xml

  1. <ehcache>
  2. <defaultCache
  3. maxElementsInMemory="1000"
  4. eternal="false"
  5. timeToIdleSeconds="600"
  6. timeToLiveSeconds="600"
  7. memoryStoreEvictionPolicy="LRU">
  8. </defaultCache>
  9. </ehcache>

配置结束,登录之后,检查多次访问需要权限控制的代码时,是否不再反复查询权限数据(是否有多次进入Realm的doGetAuthorizationInfo 方法),如果只进入一次,则代表缓存已经生效。

2. 配置属性说明

maxElementsInMemory: 缓存对象最大个数。

eternal:对象是否永久有效,一但设置了,timeout 将不起作用。

timeToIdleSeconds: 对象空闲时间,指对象在多长时间没有被访问就会失效(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。

timeToLiveSeconds:对象存活时间,指对象从创建到失效所需要的时间(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,默认是 0,也就是对象存活时间无穷大。

memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。

缓存策略一般有3种:

默认LRU(最近最少使用,距离现在最久没有使用的元素将被清出缓存)。

FIFO(先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉)。

LFU(较少使用,意思是一直以来最少被使用的,缓存的元素有一个hit 属性(命中率),hit 值最小的将会被清出缓存)。

总结

以上就是对Shiro 安全框架的总结了,代码仅供参考,欢迎讨论交流。

Shiro 安全框架详解二(概念+权限案例实现)的更多相关文章

  1. Shiro 安全框架详解一(概念+登录案例实现)

    shiro 安全框架详细教程 总结内容 一.RBAC 的概念 二.两种常用的权限管理框架 1. Apache Shiro 2. Spring Security 3. Shiro 和 Spring Se ...

  2. shiro 安全框架 详解

    ---恢复内容开始--- Shiro 简介 简介• Apache Shiro 是 Java 的一个安全(权限)框架.• Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境, ...

  3. (转) shiro权限框架详解06-shiro与web项目整合(上)

    http://blog.csdn.net/facekbook/article/details/54947730 shiro和web项目整合,实现类似真实项目的应用 本文中使用的项目架构是springM ...

  4. iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)

    转载自:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-two.html 一. 概况 本文接着 iOS 开 ...

  5. iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)

    本文链接:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-three.html 这里接着前文<iOS ...

  6. iOS 开发之照片框架详解(2)

    一. 概况 本文接着 iOS 开发之照片框架详解,侧重介绍在前文中简单介绍过的 PhotoKit 及其与 ALAssetLibrary 的差异,以及如何基于 PhotoKit 与 AlAssetLib ...

  7. iOS 开发之照片框架详解

    转载自:http://kayosite.com/ios-development-and-detail-of-photo-framework.html 一. 概要 在 iOS 设备中,照片和视频是相当重 ...

  8. hadoop框架详解

    Hadoop框架详解 Hadoop项目主要包括以下四个模块 ◆ Hadoop Common: 为其他Hadoop模块提供基础设施 ◆ Hadoop HDFS: 一个高可靠.高吞吐量的分布式文件系统 ◆ ...

  9. Spark2.1.0——内置RPC框架详解

    Spark2.1.0——内置RPC框架详解 在Spark中很多地方都涉及网络通信,比如Spark各个组件间的消息互通.用户文件与Jar包的上传.节点间的Shuffle过程.Block数据的复制与备份等 ...

随机推荐

  1. 如何为k8s中的pod配置QoS等级?

    1.概述 本文介绍如何为pod分配特定的QoS等级. 我们知道,在k8s的环境中,通过使用QoS等级来做决定,在资源紧张的时候,将哪些的pod进行驱逐,或者说如何对pod进行调度. OK,话不多说,让 ...

  2. SQL从零到迅速精通【查询利器】

    1.[列选取]从fruits表中获取f_name和f_price两列,T-SQL语句如下. SELECT f_name,f_price FROM fruits; 2.[去重]查询fruits表中s_i ...

  3. tensorflow源码解析之framework-graph

    目录 什么是graph 图构建辅助函数 graph_transfer_info 关系图 涉及的文件 迭代记录 1. 什么是graph graph是TF计算设计的载体,如果拿TF代码的执行和Java代码 ...

  4. MyBatis报错—Type handler was null on parameter mapping for property 'createTime'. It was either not specified and/or could not be found for the javaType (javax.xml.crypto.Data) : jdbcType (null) combina

    原因是:在创建实体类的时候吧date类型写成data导致类型不匹配 Type handler was null on parameter mapping for property 'createTim ...

  5. [SPDK/NVMe存储技术分析]005 - DPDK概述

    注: 之所以要中英文对照翻译下面的文章,是因为SPDK严重依赖于DPDK的实现. Introduction to DPDK: Architecture and PrinciplesDPDK概论:体系结 ...

  6. windows配置jdk环境变量、mysql环境变量、tomcat环境变量、maven环境变量、git环境变量、node环境变量

    一.windows配置各种环境变量后 path 路径下的目录: 二.windows 配置各种环境变量的目的: 为windows系统添加上各种环境对应的命令 举例,为什么要添加jdk的bin目录,是因为 ...

  7. Java注释相关以及IDEA配置相关的注释

    本文章主要包括以下6个内容: 一.注释分类以及javadoc的使用 二.使用Alibaba Java Coding Guidelines规范编码. 三.IDEA配置类注释 四.IDEA配置方法注释 = ...

  8. Java如何使用实时流式计算处理?

    我是3y,一年CRUD经验用十年的markdown程序员‍常年被誉为职业八股文选手 最近如果拉过austin项目代码的同学,可能就会发现多了一个austin-stream模块.其实并不会意外,因为这一 ...

  9. JDK8新特性关于Stream流

    在Java1.8之前还没有stream流式算法的时候,我们要是在一个放有多个User对象的list集合中,将每个User对象的主键ID取出,组合成一个新的集合,首先想到的肯定是遍历,如下: 1 2 3 ...

  10. spring源码-扩展点

    /** * @Author quan * @Date 2020/11/13 * 扩展原理 * BeanPostProcessor bean后置处理器,bean创建对象初始化前后进行拦截工作 * * * ...