Java之IO流-字符流Writer Reader

IO流:

  • IO流用来处理设备之间的数据传输

  • Java对数据的操作是通过流的方式

  • Java用于操作流的对象都在IO包中

  • 流按操作数据类型分为两种:字节流和字符流

  • 流按流向分为:输入流和输出流

先来一张网上的图,这个图很详细的描述了IO流的分类和与之对应的基类以及其他的子类。

image.png

从图中我们可以看出Java的IO流分两类

分别是字符流字节流;字符流的基类是ReaderWriter,字节流的基类是InputStreamOutputStream

字符流:

Writer:

数据最常见的形式是文件,我们就操作文件来演示。

例:在硬盘上创建一个文件,并写入一些文字数据。

需要操作文件,我们就要找到和文件相关的操作类,在上图中我们可以看到对文件操作的字符流的子类有一个FileWriter

package com.wjy329.demo;

import java.io.FileWriter;
import java.io.IOException;

/**

 * @description: FileWriter demo
 * @author: wjy329
 * @create: 2018-09-07 11:06
 **/
public class FileWriterDemo {
    public static void main(String[] args) {
        //创建一个FileWrite对象;该对象一被初始化必须要有被操作的文件,
        // 该文件会被创建到指定的目录下,如果该目录下已有同名文件会被覆盖
        FileWriter fileWriter;
        try {
             fileWriter = new FileWriter("demo.txt");
            //调用write方法,将字符串写入到流中
            fileWriter.write("wjy329");
            //刷新流对象中缓存的数据
            //将数据刷新到目标文件中
            fileWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

必须要进行异常的处理,因为我们操作的文件如果根本不存在(路径也不存在)那么就会导致程序异常,这时必须抛出。

运行代码后:发现在工程的目录下生成了demo.txt文件

image.png

如果本来已经demo.txt,那么demo.txt将会被覆盖,也就是之前的demo.txt中的数据将会被更改。

向文件中写入文字后必须对流进行刷新,不然文本是不会更新到目标文件中去的。上面的代码使用了.flush()的方法来对流刷新;

下面我们再来一个代码:

package com.wjy329.demo;

import java.io.FileWriter;
import java.io.IOException;

/**

 * @description: FileWriter demo
 * @author: wjy329
 * @create: 2018-09-07 11:06
 **/
public class FileWriterDemo {
    public static void main(String[] args) {
        //创建一个FileWrite对象;该对象一被初始化必须要有被操作的文件,
        // 该文件会被创建到指定的目录下,如果该目录下已有同名文件会被覆盖
        FileWriter fileWriter = null;
        try {
            fileWriter = new FileWriter("demo.txt");
            //调用write方法,将字符串写入到流中
            fileWriter.write("wjy329'blog");

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(fileWriter != null){
                    fileWriter.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

运行之后发现文件中也是有数据的,这里的方法是.close()

.close() 关闭流资源,在关闭流之前会刷新一次内部的缓存中的数据。

.flush()的区别: flush刷新后,流可以继续使用,close刷新后,会将流。

而且上面的代码比第一份代码更为完整,这也是我们常用的标准写法,初始化,写入和close,对应的异常处理。

对已有文件的数据续写

image.png

在官方文档中,FileWriter中有两个参数,第二参数就是判断是否在原文件后添加新的文字。

package com.wjy329.demo;

import java.io.FileWriter;
import java.io.IOException;

/**

 * @description: FileWriter demo
 * @author: wjy329
 * @create: 2018-09-07 11:06
 **/
public class FileWriterDemo {
    public static void main(String[] args) {
        //创建一个FileWrite对象;该对象一被初始化必须要有被操作的文件,
        // 该文件会被创建到指定的目录下,如果该目录下已有同名文件会被覆盖
        FileWriter fileWriter = null;
        try {
            fileWriter = new FileWriter("demo.txt",true);
            //调用write方法,将字符串写入到流中
            fileWriter.write("append test");

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(fileWriter != null){
                    fileWriter.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

参数为true时,表示在原有文件后添加文本,这时,运行发现,新添加的数据在原文本的后面。

image.png

Reader:

读取文件的常用方式:

package com.wjy329.demo;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

/**

 * @description: FileReader demo
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-09 21:54
 **/
public class FileReaderDemo {
    public static void main(String[] args) {
        //读取当前项目下的StringDemo.java文件
        FileReader fr = null ;
        try {
            fr = new FileReader("demo.txt");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        //一次读取一个字符数组
        char[] chs = new char[1024] ;
        int len = 0 ;
        try {
            while((len=fr.read(chs))!=-1) {
                System.out.println(new String(chs,0,len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //释放资源
            try {
                if(fr != null){
                    fr.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

运行程序后,控制台输出文本的内容。

image.png

拷贝文件:

拷贝是一个在考试中经常遇到的问题,这里我也学习一个例子:

package com.wjy329.demo;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**

 * @description: 拷贝文件
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-09 22:06
 **/
public class CopyDemo {
    public static void main(String[] args) {
        copy();
    }
    public static void copy(){
        FileWriter fileWriter = null;
        FileReader fileReader = null;
        try{
            //新建一个目标文件
            fileWriter = new FileWriter("demo_copy.txt");
            //读取文件操作
            fileReader = new FileReader("demo.txt");

            char[] buf = new char[1024];

            int length = 0;
            while ((length=fileReader.read(buf))!=-1){
                fileWriter.write(buf,0,length);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(fileReader != null){
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try{
                if(fileWriter != null){
                    fileWriter.close();
                }
            }catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

运行程序后,目标目录生成相应的文件并复制了文本内容。

image.png

Java内存模型(一) 初识

1、Java内存模型的抽象结构

在Java中,所有的实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享。

Java线程之间的通信由Java内存模型控制,Java内存模型决定了一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,Java内存模型定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。(本地内存是Java内存模型的一个抽象概念,并不真实存在。)Java内存模型的抽象示意图如下:

image.png

从图上看出,线程A和线程B之间要通信的话,必须要经历下面2个步骤:

(1)线程A把本地内存A中更新过的共享变量刷新到主内存中去。

(2)线程B到主内存中去读取线程A之前已更新过的共享变量。

线程之间的通信必须要经过主内存。

2、从源代码到指令序列的重排序

在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种类型。

(1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

(2)指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

(3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序,如图所示:

image.png

上述的1属于编译器重排序,2和3属于处理器重排序。这些重排序可能会导致多线程程序出现内存可见性问题。

3、并发编程模型的分类

现代的处理器使用写缓冲区临时保存向内存写入的数据。写缓冲区可以保证指令流水线持续运行,它可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟。同时,通过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的多次写,减少对内存总线的占用。虽然写缓冲区有这么多的好处,但每个处理器上的写缓冲区,仅仅对它所在的处理器可见。这个特性会对内存操作的执行顺序产生重要的影响:处理器对内存的读/写操作的执行顺序,不一定与内存实际发生的读/写操作顺序一致。

image.png

这里处理器A和处理器B可以同时把共享变量写入自己的写缓冲区(A1,B1),然后从内存中读取另一个共享变量(A2,B2),最后才把自己写缓存区中保存的脏数据刷新到内存中(A3,B3)。当以这种时序执行时,程序就可以得到x = y = 0的结果。

感觉后面实在看不明白了。。。看明白了再更新把,汗。

Springboot之shiro-动态权限

多数情况下,我们都把权限存放在数据库中,然后动态的加载,这样可以操作数据库来对权限进行增删改查。这里仅仅写如何加载数据库中查询到的权限。我这里直接用权限表的资源路径来表示;

先来一个返回的实体类,包括角色名称和资源路径,分别是从角色表和权限表中查询出来。

1、RolePerm.java

package com.wjy329.shirodemo.entity;
/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-04 10:15
 **/
public class RolePerm {
    //角色名称
    private String rname;
    //资源url
    private String url;

    public String getRname() {
        return rname;
    }

    public void setRname(String rname) {
        this.rname = rname;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

2、RolePermMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wjy329.shirodemo.dao.RolePermDao">
    <resultMap id="BaseResultMap" type="com.wjy329.shirodemo.entity.RolePerm">
        <result property="rname" column="rname"/>
        <result property="url" column="url"/>
    </resultMap>

    <select id="getRolePerm" resultMap="BaseResultMap">
        SELECT t1.rname as rname,t2.url as url
        FROM role t1,permission t2,permission_role t3
        WHERE
        t1.rid = t3.rid
        AND
        t2.pid = t3.pid
    </select>
</mapper>

3、RolePermDao.java

package com.wjy329.shirodemo.dao;

import com.wjy329.shirodemo.entity.RolePerm;

import java.util.List;

/**
 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-04 10:18
 **/
public interface RolePermDao{
    List<RolePerm> getRolePerm();
}

4、RolePermService.java

package com.wjy329.shirodemo.service;

import java.util.Map;

/**
 * @Author: wjy329
 * @Time: 2018/9/4上午10:00
 */
public interface RolePermService {
    /**
    * @Description:  获取角色和权限列表
    * @Param:
    * @return:
    * @Author: wjy329
    * @Date: 2018/9/4
    */
    Map<String,String> getRolePerm();
}

5、RolePErmServiceImpl.java

package com.wjy329.shirodemo.service.impl;

import com.wjy329.shirodemo.dao.RolePermDao;
import com.wjy329.shirodemo.entity.Role;
import com.wjy329.shirodemo.entity.RolePerm;
import com.wjy329.shirodemo.service.RolePermService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-04 10:12
 **/
@Service
public class RolePermServiceImpl implements RolePermService{
    @Autowired
    RolePermDao rolePermDao;
    @Override
    public Map<String, String> getRolePerm() {
        //获取权限信息
        List<RolePerm> perms = this.rolePermDao.getRolePerm();
        if(perms == null || perms.size() ==0){
            return null;
        }
        //这个地方需要优化权限列表信息
        //去掉重复的,一个权限可以有多个角色信息
        Set<String> urls = new HashSet<>(perms.size());
        for(RolePerm rolePerm:perms){
            if(!rolePerm.getUrl().equals("/")) {
                urls.add(rolePerm.getUrl());
            }
        }
        //获取到数据库存储的shiro信息
        Map<String,String> rolePermMap = new LinkedHashMap<>();
        for(String url : urls){
            //获取到一个url对应的角色信息
            Set<String> roles = this.getRoles(perms, url);
            String shiroRole = this.getShiroRole(roles);
            rolePermMap.put(url,shiroRole);
        }
        System.out.println("shuchu"+rolePermMap);
        return rolePermMap;
        }

    private String getShiroRole(Set<String> roles) {
        StringBuffer sb = new StringBuffer();
        for(String role : roles){
            sb.append(role);
            sb.append(",");
        }
        //去掉最后的一个 ,
        String rolesStr = sb.substring(0, sb.length()-1);
        String SHIRO_OR_ROLES = "authc,orRole"+"[%s]";
        return String.format(SHIRO_OR_ROLES,rolesStr);
    }

    private Set<String> getRoles(List<RolePerm> perms, String url) {
        Set<String> roles = new HashSet<String>();

        for(RolePerm perm:perms){
            if(perm.getUrl().equals(url)){
                //这个地方title 就是role的数据,直接映射上去的,没有加字段信息
                roles.add(perm.getRname());
            }
        }

        return roles;
    }

}

大概思路就是从角色表和权限表中获取到对应的角色和权限信息,一个权限资源可以对应多个角色,用户有任一角色都可访问该资源,然后需要转换成shiro中的格式上面的注释中也写了出来,直接写roles[admin,customer]的话会导致用户必须同时拥有这两种角色才能访问,所以需要自定义一个拦截器,使其有任一角色即可访问。

6、orRole 自定义拦截器-CustomRolesAuthorizationFilter.java

package com.wjy329.shirodemo.config;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

// AuthorizationFilter抽象类事项了javax.servlet.Filter接口,它是个过滤器。
public class CustomRolesAuthorizationFilter extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception {
        Subject subject = getSubject(req, resp);
        String[] rolesArray = (String[]) mappedValue;

        if (rolesArray == null || rolesArray.length == 0) { //没有角色限制,有权限访问
            return true;
        }
        for (int i = 0; i < rolesArray.length; i++) {
            if (subject.hasRole(rolesArray[i])) { //若当前用户是rolesArray中的任何一个,则有权限访问
                return true;
            }
        }

        return false;
    }
}

7、加载自定义拦截器

需要在shiro的配置中添加

Map<String,Filter> map = new HashedMap();
map.put("orRole",orRole());

bean.setFilters(map);

。。。

//自定义拦截器
@Bean("orRole")
public CustomRolesAuthorizationFilter  orRole(){
   return new CustomRolesAuthorizationFilter ();

}

具体如下:

package com.wjy329.shirodemo.config;

import com.wjy329.shirodemo.service.RolePermService;
import org.apache.commons.collections.map.HashedMap;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**

 * @description: shiro配置类
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 19:54
 **/
@Configuration
public class ShiroConfig {
    @Autowired
    RolePermService rolePermService;

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactory(@Qualifier("securityManager") SecurityManager manager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //添加自定义拦截器
        Map<String,Filter> map = new HashedMap();
        map.put("orRole",orRole());
        bean.setFilters(map);
        
        
        bean.setSecurityManager(manager);

        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/unauth");

        LinkedHashMap<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/index","authc");
        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/loginUser","anon");
        filterChainDefinitionMap.put("/logout","logout");
        filterChainDefinitionMap.put("/admin","roles[admin]");
        filterChainDefinitionMap.putAll(rolePermService.getRolePerm());
        filterChainDefinitionMap.put("/**", "roles[admin]");

        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);


        return bean;
    }

    @Bean("securityManager")
    public SecurityManager securityManager(AuthRealm authRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }

    @Bean("authRealm")
    public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher){
        AuthRealm authRealm = new AuthRealm();
        authRealm.setCacheManager(new MemoryConstrainedCacheManager());
        authRealm.setCredentialsMatcher(matcher);
        return authRealm;
    }

    @Bean("credentialMatcher")
    public CredentialMatcher credentialMatcher(){
        return new CredentialMatcher();
    }

    //自定义拦截器
    @Bean("orRole")
    public CustomRolesAuthorizationFilter  orRole(){
        return new CustomRolesAuthorizationFilter ();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
}

这样就可以读取出数据库中的权限信息了,后面如果要对权限进行增删改查即可对角色和权限表操作,动态更新的话,我觉得应该就是重新加载权限信息,具体的实现以后遇到了再说吧。

Springboot学习之shiro整合

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障 – 从命令行应用、移动应用到大型网络及企业应用。关于shiro的原理和其他的资料可以自行百度,这里仅记录shiro的用法。

准备:

开发工具:IDEA

Java版本:1.8

Mysql:5.7

1、环境搭建

maven依赖-pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.wjy329</groupId>
   <artifactId>shiro-demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>shiro-demo</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.10.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>
      <!-- thymeleaf模板 -->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.mybatis.spring.boot</groupId>
         <artifactId>mybatis-spring-boot-starter</artifactId>
         <version>1.3.2</version>
      </dependency>

      <!-- spring.thymeleaf.mode=LEGACYHTML5 -->
      <dependency>
         <groupId>net.sourceforge.nekohtml</groupId>
         <artifactId>nekohtml</artifactId>
      </dependency>

      <dependency>
         <groupId>org.apache.httpcomponents</groupId>
         <artifactId>httpclient</artifactId>
      </dependency>
      
      <!-- 数据库驱动 -->
      <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <scope>runtime</scope>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      <!-- shiro依赖-->
      <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-core</artifactId>
         <version>1.2.5</version>
      </dependency>
      <!-- shiro spring包 -->
      <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-spring</artifactId>
         <version>1.2.5</version>
      </dependency>

      <!-- 数据库连接池-->
      <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid</artifactId>
         <version>1.1.0</version>
      </dependency>

      <!-- apache工具包 -->
      <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
         <version>3.5</version>
      </dependency>

      <!-- spring工具包 -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-context-support</artifactId>
         <version>5.0.6.RELEASE</version>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>


</project>

配置文件-application.properties

## 数据库 ##
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8
## 数据库用户名和密码 ##
spring.datasource.username=root
spring.datasource.password=admin

## mybatis ##
mybatis.mapper-locations=mapper/*.xml
mybatis.type-aliases-package=com.wjy329.shirodemo.entity

#Thymeleaf
#thymeleaf start
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
#\u5f00\u53d1\u65f6\u5173\u95ed\u7f13\u5b58,\u4e0d\u7136\u6ca1\u6cd5\u770b\u5230\u5b9e\u65f6\u9875\u9762
spring.thymeleaf.cache=false
# \u5728\u6784\u5efaURL\u65f6\u9884\u5148\u67e5\u770b\u540d\u79f0\u7684\u524d\u7f00 \uff08\u9ed8\u8ba4\u5c31\u662f\u8fd9\u4e2a\uff09
spring.thymeleaf.prefix=classpath:/templates/
# \u6784\u5efaURL\u65f6\u9644\u52a0\u67e5\u770b\u540d\u79f0\u7684\u540e\u7f00.\uff08\u9ed8\u8ba4\u5c31\u662f html\u7684\u7ed3\u5c3e\u7684\uff09
spring.thymeleaf.suffix=.html
#thymeleaf end

2、使用

权限管理包括三个实体:用户、角色、权限,所以我们需要新建三个实体类,分别是:User.javaRole.javaPermission.java

User.java

package com.wjy329.shirodemo.entity;

import java.util.HashSet;
import java.util.Set;

/**

 * @description: 用户实体
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 14:48
 **/
public class User {
    /**
     * 用户id
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private Integer uid;

    /**
     * 用户名称
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private String username;

    /**
     * 用户密码
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private String password;

    /**
     * 用户角色
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private Set<Role> roles = new HashSet<>();

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

Role.java:

package com.wjy329.shirodemo.entity;

import java.util.HashSet;
import java.util.Set;

/**

 * @description: 角色实体
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 14:49
 **/
public class Role {
    /**
     * 角色id
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private Integer rid;

    /**
     * 角色名称
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private String name;

    /**
     * 角色权限
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private Set<Permission> permissions = new HashSet<>();

    /**
     * 一个角色包含多个用户
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private Set<User> users = new HashSet<>();

    public Integer getRid() {
        return rid;
    }

    public void setRid(Integer rid) {
        this.rid = rid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(Set<Permission> permissions) {
        this.permissions = permissions;
    }

    public Set<User> getUsers() {
        return users;
    }

    public void setUsers(Set<User> users) {
        this.users = users;
    }
}

Permission.java:

package com.wjy329.shirodemo.entity;

/**

 * @description: 权限实体
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 14:49
 **/
public class Permission {

    /**
    * 权限id
    * @Author: wjy329
    * @Date: 2018/9/3
    */
    private Integer pid;

   /**
    * 权限名称
    * @Author: wjy329
    * @Date: 2018/9/3
    */
    private String name;

    /**
     * 资源url
     * @Author: wjy329
     * @Date: 2018/9/3
     */
    private String url;

    public Integer getPid() {
        return pid;
    }

    public void setPid(Integer pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

权限控制需要对应5张表,分别是:user(用户表)、role(角色表)、permission(权限表)、user_role(用户角色表)、permission_role(角色权限表)  这里我直接给出建表的sql,方便大家生成:

init.sql

-- 权限表 --
CREATE TABLE permission(
  pid INT(11) NOT NULL AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL DEFAULT '',
  url VARCHAR (255) DEFAULT '',
  PRIMARY KEY (pid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO permission VALUES ('1','add','');
INSERT INTO permission VALUES ('2','delete','');
INSERT INTO permission VALUES ('3','edit','');
INSERT INTO permission VALUES ('4','query','');

-- 用户表 --
CREATE TABLE user(
  uid int(11) NOT NULL AUTO_INCREMENT,
  username VARCHAR(255) NOT NULL DEFAULT '',
  password VARCHAR(255) NOT NULL DEFAULT '',
  PRIMARY KEY (uid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO user VALUES ('1','admin','123');
INSERT INTO user VALUES ('2','demo','123');

-- 角色表 --
CREATE TABLE role(
  rid int(11) NOT NULL AUTO_INCREMENT,
  rname VARCHAR (255) NOT NULL DEFAULT '',
  PRIMARY KEY (rid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO role VALUES ('1','admin');
INSERT INTO role VALUES ('2','customer');

-- 权限角色关系表 --
CREATE TABLE permission_role(
  rid int(11) NOT NULL ,
  pid int (11) NOT NULL ,
  KEY idx_rid (rid),
  KEY idx_pid (pid)
)ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO permission_role VALUES ('1','1');
INSERT INTO permission_role VALUES ('1','2');
INSERT INTO permission_role VALUES ('1','3');
INSERT INTO permission_role VALUES ('1','4');
INSERT INTO permission_role VALUES ('2','1');
INSERT INTO permission_role VALUES ('2','4');

-- 用户角色关系表 --
CREATE TABLE user_role(
  uid int(11) NOT NULL ,
  pid int (11) NOT NULL ,
  KEY idx_uid (uid),
  KEY idx_rid (rid)
)ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO user_role VALUES ('1','1');
INSERT INTO user_role VALUES ('2','2');

执行完该段sql后,会生成对应的表,接下来,我们写mybatis的东西,需要在resources文件夹下新建一个mapper文件夹,用来存放对应的mapper.xml

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.wjy329.shirodemo.dao.UserDao">
    <resultMap id="userMap" type="com.wjy329.shirodemo.entity.User">
        <id property="uid" column="uid"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <collection property="roles" ofType="com.wjy329.shirodemo.entity.Role">
            <id property="rid" column="rid"/>
            <result property="rname" column="rname"/>
            <collection property="permissions" ofType="com.wjy329.shirodemo.entity.Permission">
                <id property="pid" column="pid"/>
                <result property="name" column="name"/>
                <result property="url" column="url"/>
            </collection>
        </collection>
    </resultMap>

    <select id="findByUsername" parameterType="String" resultMap="userMap">
      SELECT u.*,r.*,p.*
      FROM user u
        INNER JOIN user_role ur on ur.uid = u.uid
        INNER JOIN role r on r.rid = ur.rid
        INNER JOIN permission_role pr on pr.rid = r.rid
        INNER JOIN permission p on pr.pid = p.pid
      WHERE u.username = #{username}
    </select>
</mapper>

UserDao.java

package com.wjy329.shirodemo.dao;

import com.wjy329.shirodemo.entity.User;
import org.apache.ibatis.annotations.Param;

/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 15:11
 **/
public interface UserDao {

    /**
    * @Description:  根据用户名查找用户信息
    * @Param:
    * @return:
    * @Author: wjy329
    * @Date: 2018/9/3
    */
    User findByUsername(@Param(value = "username")String username);
}

UserService.java

package com.wjy329.shirodemo.service;

import com.wjy329.shirodemo.entity.User;

/**
 * @Author: wjy329
 * @Time: 2018/9/3下午3:16
 */
public interface UserService {

    User findByUsername(String username);
}

UserServiceImpl.java

package com.wjy329.shirodemo.service.impl;

import com.wjy329.shirodemo.dao.UserDao;
import com.wjy329.shirodemo.entity.User;
import com.wjy329.shirodemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 15:17
 **/
@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserDao userDao;
    @Override
    public User findByUsername(String username) {
        return userDao.findByUsername(username);
    }
}

TestController.java

package com.wjy329.shirodemo.controller;

import com.wjy329.shirodemo.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;

/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 20:11
 **/
@Controller
public class TestController {

    @RequestMapping("/login")
    public String login(){
        return "login";
    }

    @RequestMapping("/index")
    public String index(){
        return "index";
    }

    @RequestMapping("/unauth")
    @ResponseBody
    public String unauth(){
        return "未授权";
    }

    @RequestMapping("/loginUser")
    public String loginUser(@RequestParam("username")String username,
                            @RequestParam("password")String password,
                            HttpSession httpSession){
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        Subject subject = SecurityUtils.getSubject();
        try{
            subject.login(token);
            User user = (User) subject.getPrincipal();
            httpSession.setAttribute("user",user);
            return "index";
        }catch (Exception e){
            return "login";
        }
    }

    @RequestMapping("/admin")
    @ResponseBody
    public String admin(){
        return "welcome admin";
    }
}

前面的东西都是我们熟知的,接下来才是shiro真正的开始。

shiro从Realm中获取安全数据,也就是说,从Realm中获取用户、角色、权限,可以把Realm看成shiro的安全数据源。我们就从自定义Realm开始,毕竟有了数据源再看其他的才会有意义。新建一个config的配置包,这里面存放shiro的相关配置;

AuthRealm.java

package com.wjy329.shirodemo.config;

import com.wjy329.shirodemo.entity.Permission;
import com.wjy329.shirodemo.entity.Role;
import com.wjy329.shirodemo.entity.User;
import com.wjy329.shirodemo.service.UserService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;


import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**

 * @description:
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 19:18
 **/
public class AuthRealm extends AuthorizingRealm{

    @Autowired
    private UserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();
        //权限列表
        List<String> permissionList = new ArrayList<>();
        //角色列表
        List<String> roleNameList = new ArrayList<>();
        //获取用户角色
        Set<Role> roleSet = user.getRoles();
        if(CollectionUtils.isNotEmpty(roleSet)){
            for(Role role : roleSet){
                roleNameList.add(role.getRname());
                Set<Permission> permissionSet = role.getPermissions();
                if(CollectionUtils.isNotEmpty(permissionSet)){
                    for(Permission permission : permissionSet){
                        permissionList.add(permission.getName());
                    }
                }
            }
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissionList);
        info.addRoles(roleNameList );
        return info;
    }

    //认证登录
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String username = usernamePasswordToken.getUsername();
        User user = userService.findByUsername(username);
        return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());
    }
}

我们可以看到,自定义Realm先要继承AuthorizingRealm并实现两个方法,一个是认证(AuthenticationInfo),一个是授权(AuthorizationInfo),注释中也相应的标示了出来。

比对密码还需要我们自定义CredentialMatcher

CredentialMatcher.java

package com.wjy329.shirodemo.config;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

/**

 * @description: 自定义密码规则
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 19:50
 **/
public class CredentialMatcher extends SimpleCredentialsMatcher{
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        //用户输入的密码
        String password = new String(usernamePasswordToken.getPassword());
        //数据库中保存的密码
        String dbPassword = (String) info.getCredentials();
        return this.equals(password,dbPassword);
    }
}

上面的密码匹配规则仅仅是简单对比,实际使用中,我们的密码可能采用密码+盐或者其他加密解密认证方式,我们只需要在这里做相应的更改即可,总之就是比对输入的密码是否正确。

认证授权和密码校验都有了,这两个东西放在哪呢?那必须是放在shiro的配置中。新建一个配置类:

ShiroConfig.java

package com.wjy329.shirodemo.config;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

/**

 * @description: shiro配置类
 *
 * @author: wjy329
 * @param:
 * @return:
 * @create: 2018-09-03 19:54
 **/
@Configuration
public class ShiroConfig {

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactory(@Qualifier("securityManager") SecurityManager manager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);

        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/unauth");

        LinkedHashMap<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/index","authc");
        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/loginUser","anon");
        filterChainDefinitionMap.put("/admin","roles[admin]");
        filterChainDefinitionMap.put("/**","user");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return bean;
    }
    
    @Bean("securityManager")
    public SecurityManager securityManager(AuthRealm authRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }
    //加载自定义的realm
    @Bean("authRealm")
    public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher){
        AuthRealm authRealm = new AuthRealm();
        authRealm.setCredentialsMatcher(matcher);
        return authRealm;
    }
    //加载自定义的密码匹配
    @Bean("credentialMatcher")
    public CredentialMatcher credentialMatcher(){
        return new CredentialMatcher();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
}

上面这些方法基本都是从下往上一层层被调用,我们也可以看明白是在干什么;

shiroFilterFactory方法基本就是拦截了,这里需要注意下拦截器

默认拦截器名

拦截器类

说明(括号里的表示默认值)

身份验证相关的

authc

org.apache.shiro.web.filter.authc

.FormAuthenticationFilter

基于表单的拦截器;如 “`/**=authc`”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username);  passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe);  loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储 key(shiroLoginFailure);

authcBasic

org.apache.shiro.web.filter.authc

.BasicHttpAuthenticationFilter

Basic HTTP 身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application);

logout

org.apache.shiro.web.filter.authc

.LogoutFilter

退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/); 示例 “/logout=logout”

user

org.apache.shiro.web.filter.authc

.UserFilter

用户拦截器,用户已经身份验证 / 记住我登录的都可;示例 “/**=user”

anon

org.apache.shiro.web.filter.authc

.AnonymousFilter

匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例 “/static/**=anon”

授权相关的

roles

org.apache.shiro.web.filter.authz

.RolesAuthorizationFilter

角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例 “/admin/**=roles[admin]”

perms

org.apache.shiro.web.filter.authz

.PermissionsAuthorizationFilter

权限授权拦截器,验证用户是否拥有所有权限;属性和 roles 一样;示例 “/user/**=perms["user:create"]”

port

org.apache.shiro.web.filter.authz

.PortFilter

端口拦截器,主要属性:port(80):可以通过的端口;示例 “/test= port[80]”,如果用户访问该页面是非 80,将自动将请求端口改为 80 并重定向到该 80 端口,其他路径 / 参数等都一样

rest

org.apache.shiro.web.filter.authz

.HttpMethodPermissionFilter

rest 风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例 “/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete” 权限字符串进行权限匹配(所有都得匹配,isPermittedAll);

ssl

org.apache.shiro.web.filter.authz

.SslFilter

SSL 拦截器,只有请求协议是 https 才能通过;否则自动跳转会 https 端口(443);其他和 port 拦截器一样;

其他

noSessionCreation

org.apache.shiro.web.filter.session

.NoSessionCreationFilter

不创建会话拦截器,调用 subject.getSession(false) 不会有什么问题,但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常;

然后我们写相关的页面:

主页index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>

<h1>你好,<span th:text="${session.user.username}" >用户名</span> </h1>
</body>
</html>

登录页面login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>

<h1> 欢迎登录 </h1>
<form action="/loginUser" method="post">
    <input type="text" name="username"> <br>
    <input type="password" name="password"> <br>
    <input type="submit" value="提交">
</form>
</body>
</html>

到这里呢,shiro的配置基本完成了,我们可以测试一下。

先访问主页,http://localhost:8080/index  由于未登录,直接就跳到了登录界面

index.gif

我们输入用户名密码 admin  123,发现此时跳转到登录成功后的主页

login.gif

接下来访问 admin 也是成功状态

image.png

我们再换普通角色的用户来演示: demo 123

demo.gif

我们发现它跳转到了未授权界面,这个是上面的拦截器那里配置的;我们会发现以后需要动态的添加权限怎么办呢?总不可能每次添加一个权限就是修改一下代码吧?那当然不需要了,下篇我们就研究如何动态的加载权限。

Springboot之Hibernate自动建表

1、引入Maven依赖包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

2、修改配置文件  application.properties

spring.profiles.active=dev
###############################
#数据库用户名
spring.datasource.username=root
#数据库密码
spring.datasource.password=admin
#数据库驱动
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#数据库连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/study?useUnicode=true&characterEncoding=UTF-8&&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull
#JPA Configuration:
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.hbm2ddl.auto=update

3、创建实体类

UserInfo.java

package com.wjy329.studyshiro.entity;

import java.io.Serializable;
import java.util.List;
import javax.persistence.*;

/**
 * @author wjy329
 * @date 2018/8/23
 * @description ${description}
 */
@Entity
@Table(name = "user_info")
public class UserInfo implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id@GeneratedValue
    private long uid;//用户id;
    @Column(unique=true)
    private String username;//账号.
    private String name;//名称(昵称或者真实姓名,不同系统不同定义)
    private String password; //密码;
    private String salt;//加密密码的盐
    private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
    private List<SysRole> roleList;// 一个用户具有多个角色
    public List<SysRole> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<SysRole> roleList) {
        this.roleList = roleList;
    }

    public long getUid() {
        return uid;
    }

    public void setUid(long uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public byte getState() {
        return state;
    }

    public void setState(byte state) {
        this.state = state;
    }

    /**
     * 密码盐.
     * @return
     */
    public String getCredentialsSalt(){
        return this.username+this.salt;
    }

    @Override
    public String toString() {
        return "UserInfo [uid=" + uid + ", username=" + username + ", name=" + name + ", password=" + password
                + ", salt=" + salt + ", state=" + state + "]";
    }
}

注意注解的添加: @Entity @Table @Id @GeneratedValue

最后运行启动类即可,就会在数据库中生成相应的表。

图片.png

Java学习之线程池(二)-Executor框架

1、Executor框架的结构

Executor框架由三部分组成:

  • 任务

  • 任务的执行

  • 异步计算的结果

步骤: 

1、主线程首先要创建实现Runnable或者Callable接口的任务对象。工具类Executors可以把一个Runnable对象封装为一个Callable对象或Executors.callable

2、然后可以把Runnable对象直接交给ExecutorService执行;

3、最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。

2、Executor框架的成员

ThreadPoolExecutor、ScheduledThreadPoolExecutor、Future接口、Runnable接口、Callable接口和Executors;

(1)ThreadPoolExecutor 通常使用工厂类Executors来创建。Executors可以创建3种类型的ThreadPoolExecutor:

    SingleThreadExecutor、FixedThreadPool和CachedThreadPool

  •     FixedThreadPool   创建使用固定线程数的线程池;适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,适用于负载比较重的服务器。

  •     SingleThreadExecutor  创建使用单个线程的线程池;适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。

  •     CachedThreadPool  创建一个会根据需要创建新线程的线程池;适用于执行很多的短期异步任务的小程序。

(2)ScheduledThreadPoolExecutor 通常使用工厂类Executors来创建。可以创建两种类型的ScheduledThreadPoolExecutor:

  •     ScheduledThreadPoolExecutor 包含若干个线程;适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。

  •    SingleScheduledThreadPoolExecutor 只包含一个线程;适用于需要单个后台线程执行周期任务,同时需要保证顺序的执行各个任务的应用场景。

   

Java学习之线程池(一)

最近的项目中使用了多线程,是直接new Thread(),发现总会有数据丢失的情况,感觉上是线程太多导致一些线程没抢到资源起不来。先用线程池实现多线程的运行,看看是否是多线程的问题。在这里记录下线程池的使用。

1、线程池的优点

  • 降低资源消耗

  • 提高响应速度

  • 提高线程的可管理性

2、线程池处理流程

(1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。

(2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

(3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

3、ThreadPoolExecutor 的使用

//创建线程池
private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10,15, 3,
 TimeUnit. SECONDS, new ArrayBlockingQueue<Runnable>(3),
 new ThreadPoolExecutor.DiscardOldestPolicy());
 
 <!--private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(int corePoolSize,
 int maximumPoolSize, long keepAliveTime,TimeUnit unit, 
 BlockingQueue<Runnable> paramBlockingQueue,
 RejectedExecutionHandler handler);  -->

//执行线程
Thread t = new Thread();
threadPool.execute(t);

如上面代码所示,线程池的使用其实很简单,就是搞清楚这几个参数是干嘛的就可以了。

corePoolSize:线程池核心线程数量

maximumPoolSize:线程池最大线程数量 (>=corePoolSize

keepAliveTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间

unit:存活时间的单位

paramBlockingQueue:存放任务的队列

handler:超出线程范围和队列容量的任务的处理程序

paramBlockingQueue类型选择

1)有界任务队列ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

2)无界任务队列LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

3)直接提交队列synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

handler策略:

AbortPolicy:丢弃任务并抛出RejectedExecutionException

CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

DiscardOldestPolicy:丢弃队列中最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。

DiscardPolicy:丢弃任务,不做任何处理。

后续将写java常用的四种线程池。

参考:https://www.cnblogs.com/superfj/p/7544971.html

        《Java并发编程的艺术》


Java学习之Serializable接口

我们在实体类的定义过程中,经常会用到Serialiazable接口,发现实现了该接口后,并没有实现该接口中的任何方法,那么这个接口的作用是干嘛的呢?

实现serializable接口的作用是就是实现序列化,可以把对象存到字节流,然后可以将字节流恢复为对象。

序列化后,字节流中包含了对象的信息,没有序列化将不能输出到字节流,无法保存对象信息。

下面我们演示一个实例:

先定义一个实体类并实现Serializable接口:

1、Person.java

package entity;

import java.io.Serializable;

/**
 * @Author: wjy329
 * @Time: 2018/8/18上午8:26
 */


public class Person implements Serializable{

    String name;
    int age;
    private static final long serialVersionUID = 1L;

    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return "name:"+name+"\tage:"+age;
    }


}

2、写出对象信息

用ObjectOutputStream的writeObject()方法把这个类的对象写到文件,再通过ObjectInputStream的readObject()方法把这个对象读出来。

import entity.Person;

import java.io.*;

/**
 * @Author: wjy329
 * @Time: 2018/8/18上午8:25
 */

public class StudySerializable {
    public static void main(String[] args) {
        //初始化一个实体
        Person person = new Person("wjy",24);
        System.out.println(person);
        File file = new File("/Users/wjy329/desktop/out.txt");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(fos);
                oos.writeObject(person);
                oos.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                try {
                    oos.close();
                } catch (IOException e) {
                    System.out.println("oos关闭失败:"+e.getMessage());
                }
            }
        } catch (FileNotFoundException e) {
            System.out.println("找不到文件:"+e.getMessage());
        } finally{
            try {
                fos.close();
            } catch (IOException e) {
                System.out.println("fos关闭失败:"+e.getMessage());
            }
        }

        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(fis);
                try {
                    Person person1 = (Person)ois.readObject();
                    System.out.println(person1);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                try {
                    ois.close();
                } catch (IOException e) {
                    System.out.println("ois关闭失败:"+e.getMessage());
                }
            }
        } catch (FileNotFoundException e) {
            System.out.println("找不到文件:"+e.getMessage());
        } finally{
            try {
                fis.close();
            } catch (IOException e) {
                System.out.println("fis关闭失败:"+e.getMessage());
            }
        }
    }
}

运行代码后,控制台输出:

image.png

第一行打印的是初始化对象的信息,第二行是读取对象的信息。

将对象初始化后,然后输出到文件

image.png

然后在其他程序中只要是相同的对象并且对象中声明的serialVersionUID相同即可以将该文件中的信息转换为对象。

3、serialVersionUID

这个可以理解为序列化的id,只有id相同才代表是相同的对象,比如定义两个Person.java,即使其它属性相同,serialVersionUID不同也不能将对象信息读取出来。一般情况下默认值为1L,我们可以定义为其它的Long型值。


Java多线程学习(二)之卖票例子

多个窗口同时卖票,要保证不能卖出同一张票,即一个座位不能卖给多个人。

 1、继承Thread

/**
 * @Author: wjy329
 * @Time: 2018/8/16下午9:44
 */

public class TicketDemo {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        Ticket t4 = new Ticket();

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class Ticket extends Thread{
    //初始化票数
    private int tick = 100;
    @Override
    public void run() {
        while (true){
            if(tick > 0){
                System.out.println(currentThread().getName()+"--已售出:"+tick--);
            }
        }
    }
}

此时运行结果:

image.png

会发现多个线程售出了同一张票。

我们只需在定义票数时设置为静态即可。

private static int tick =100;

/**
 * @Author: wjy329
 * @Time: 2018/8/16下午9:44
 */
public class TicketDemo {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        Ticket t4 = new Ticket();

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class Ticket extends Thread{
    //初始化票数
    private static int tick = 100;
    @Override
    public void run() {
        while (true){
            if(tick > 0){
                System.out.println(currentThread().getName()+"--已售出:"+tick--);
            }
        }
    }
}

此时运行结果:

image.png

多个线程将100张票都卖完。

一般情况下,我们不定义静态,静态的生命周期较长;这时,换用下面的方法

/**
 * @Author: wjy329
 * @Time: 2018/8/16下午9:44
 */
public class TicketDemo {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();

        t1.start();
        t1.start();
        t1.start();
        t1.start();
    }
}

class Ticket extends Thread{
    //初始化票数
    private  int tick = 100;
    @Override
    public void run() {
        while (true){
            if(tick > 0){
                System.out.println(currentThread().getName()+"--已售出:"+tick--);
            }
        }
    }
}

运行结果:

image.png

我们可以看出虽然100张票卖完了,但是会出现异常而且只有一个线程。这是因为线程已经start,多次start会出现线程状态异常。

2、实现Runnable接口 

这时我们采用实现Runnable接口的方式

/**
 * @Author: wjy329
 * @Time: 2018/8/16下午9:44
 */

public class TicketDemo1 {
    public static void main(String[] args) {
        Ticket1 t = new Ticket1();

       Thread t1 = new Thread(t);
       Thread t2 = new Thread(t);
       Thread t3 = new Thread(t);
       Thread t4 = new Thread(t);

       t1.start();
       t2.start();
       t3.start();
       t4.start();


    }
}

class Ticket1 implements Runnable{
    //初始化票数
    private  int tick = 1000;
    @Override
    public void run() {
        while (true){
            if(tick > 0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--已售出:"+tick--);
            }
        }
    }
}

image.png

看运行结果,发现出现了-1、-2,这意味着线程出现了安全问题。

tick属于共享数据,当1线程售出1时,此时tick>0,线程0,2,3进入,在执行减减操作,会出现负数。

3、synchronized

当我们采用synchronized时,即可保证线程安全;

/**
 * @Author: wjy329
 * @Time: 2018/8/16下午9:44
 */
public class TicketDemo1 {
    public static void main(String[] args) {
        Ticket1 t = new Ticket1();

       Thread t1 = new Thread(t);
       Thread t2 = new Thread(t);
       Thread t3 = new Thread(t);
       Thread t4 = new Thread(t);

       t1.start();
       t2.start();
       t3.start();
       t4.start();


    }
}

class Ticket1 implements Runnable{
    //初始化票数
    private  int tick = 1000;
    @Override
    public void run() {
        while (true){
            synchronized (this){
                if(tick > 0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"--已售出:"+tick--);
                }
            }
        }
    }
}

image.png

此时,通过4个线程将所有的票卖完。

后面再详细写synchronized

Java多线程学习(一)之线程的创建方式

线程是进程中的一个独立的控制单元。

一、创建线程的方式

1、继承Thread类

步骤:

    1、定义类继承Thread

    2、复写run方法

    3、调用线程的start方法;

public class ThreadTest1 {
    public static void main(String[] args) {
        ThreadDemo demo = new ThreadDemo();
        demo.start();
    }

}

class ThreadDemo extends Thread{
    @Override
    public void run() {
    //run方法中放需要执行的代码
        System.out.println("run---");
    }
}

2、实现Runnable接口

步骤:

    1、定义类实现Runnable接口

    2、覆盖run方法

    3、通过Thread类创建线程对象

    4、将Runnable接口的子类对象作为实际参数传递给Thread的构造函数

    5、调用Thread类的start方法开启线程

public class ThreadTest2{
    public static void main(String[] args) {
      ThreadDemo1 threadDemo1 = new ThreadDemo1();
      Thread t1 = new Thread(threadDemo1);
      t1.start();
    }
}

class ThreadDemo1 implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<600;i++){
            System.out.println(Thread.currentThread().getName()+"run---"+i);
        }
    }
}