问题引人:给系统加入权限控制,如普通人员可以查看本部门人员列表时,但是每个人员的工资信息,普通人员看不到。而部门经理可以看到
所有的权限系统都分为两个部分,一个是授权部分,一个是验证部分,为了理解它们,引人两个基本名词:安全实体和权限
安全实体:就是被权限系统检测的对象,比如工资数据
权限:就是需要被校验的权限对象,比如查看、修改等。
两者要一起描述才有意义。
授权:是指将对某些安全实体的某些权限分配给某些人员的过程。
验证:值判断某个人员对某个安全实体是否拥有某个或某些权限
多数采用数据库来存储授权过程产生的数据,也就是说:授权是向数据库添加数据,验证是从数据库中获取相应数据进行匹配。
假设授权过程已经完成:
张三 人员列表 查看的权限
李四 人员列表 查看的权限
李四 工资数据 查看的权限
李四 工资数据 修改的权限
试想,每次登陆都要到数据库中查询,是多么的耗时。
为了加快速度,采用一定的缓存。当人员登录的时候,就把该人员能操作的权限获取到,存储在内存中。这样每次操作进行权限验证时,从缓存中获取验证信息,典型的空间换时间。
如不使用设计模式的解决示例,其问题,1,缓存时间长度的问题,即这些数据应该被缓存多久。如果是web应用,这种跟登录人员相关的权限数据,大多放在session中缓存,
2,缓存数据与真实数据的同步问题3,缓存的多线程并发控制,有时要从缓存中取值,有时要加值。
不用设计模式的解决方法:
权限表
package notusingmode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* 在内存中模拟数据库,准备点测试数据
*/
public class TempDB {
private TempDB() { //不允许外部创建该对象
}
/**
* 记录每个人的权限
*/
public static Collection<String> colDB = new ArrayList<String>();
static {
// 填充测试数据
colDB.add("张三,人员列表,查看");
colDB.add("李四,人员列表,查看");
colDB.add("李四,工资数据,查看");
colDB.add("李四,工资数据,修改");
// 增加更多的权限
for(int i = 0;i<3;i++){
colDB.add("张三"+i+",人员列表,查看");
}
}
}
授权模型
package notusingmode;
//授权模型
public class AuthorizationModel {
private String user; //用户
private String securityEntity; //安全实体
private String permit; //权限
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getSecurityEntity() {
return securityEntity;
}
public void setSecurityEntity(String securityEntity) {
this.securityEntity = securityEntity;
}
public String getPermit() {
return permit;
}
public void setPermit(String permit) {
this.permit = permit;
}
// public String toString(){
// return user+"对"+this.securityEntity+"拥有"+permit+"的权限";
// }
//
}
安全管理的业务逻辑
package notusingmode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
//安全管理,实现成单例
public class SecurityMgr {
// 缓存权限
private Map<String, Collection<AuthorizationModel>> map = new HashMap<String, Collection<AuthorizationModel>>();
private static SecurityMgr sm = new SecurityMgr();
private SecurityMgr() {
}
public static SecurityMgr getInstance() {
return sm;
}
// 模拟登录的功能
public void login(String user) {
// 登录时,就需要把该用户拥有的权限,从数据库中取出来,放入缓存
Collection<AuthorizationModel> col = queryByUser(user);
map.put(user, col);
}
private Collection<AuthorizationModel> queryByUser(String user) {
Collection<AuthorizationModel> col = new ArrayList<AuthorizationModel>();
for (String s : TempDB.colDB) {
String ss[] = s.split(",");
if (ss[0].equals(user)) {
AuthorizationModel am = new AuthorizationModel();
am.setUser(user);
am.setPermit(ss[2]);
am.setSecurityEntity(ss[1]);
col.add(am);
}
}
return col;
}
// 判断某个用户对某个安全实体是否拥有对应的权限
public boolean hasPermit(String user, String securityEntity, String permit) {
Collection<AuthorizationModel> col = map.get(user);
if (col == null || col.size() == 0) {
System.out.println(user + "没有登录或是没有被分配任何权限");
return false;
}
for (AuthorizationModel am : col) {
System.out.println("am==" + am);
if (am.getSecurityEntity().equals(securityEntity)
&& am.getPermit().equals(permit)) {
return true;
}
}
return false;
}
}
package notusingmode;
public class Client {
public static void main(String[] args) {
SecurityMgr mgr = SecurityMgr.getInstance();
mgr.login("张三");
mgr.login("李四");
boolean f1 = mgr.hasPermit("张三","工资数据","查看");
boolean f2 = mgr.hasPermit("李四","工资数据","查看");
System.out.println("f1=" + f1);
System.out.println("f1=" + f2);
for(int i = 0;i<3;i++){
mgr.login("张三"+i);
mgr.hasPermit("张三"+i,"工资数据","查看");
}
}
}
在java中,默认的equals方法比较的是内存地址,而equals和hashCode方法的关系是,如果equals返回为true,两个实例的hashCode必须相同,反之则不成立
上面的实例,存在大量的细粒度对象,而且存在大量的重复数据,严重耗费内存,如何解决?————》享元模式
定义:运用共享技术有效地支持大量细粒度的对象。
需要分离出被换成对象实例中,哪些数据是不变且重复出现的,哪些数据是经常变化的,真正应该被缓存的数据就是那些不变的,把
它们称为对象的内部状态,而变化的数据就不缓存了,称为对象的外部状态。
在实现时,把内部状态分离出来共享,叫享元,享元工厂用来管理享元。
结构:
FlyWeight:享元接口,通过这个接口传入外部的状态,在享元对象的方法处理中可能会使用这些外部的数据。
ConcreteFlyWeight:具体的享元实现对象,必须是可共享的,需要封装FlyWeight的内部状态。
UnsharedConcreteFlyWeight:非共享的享元实现对象,并不是所有的FlyWeight实现对象都需要共享,非共享的享元实现对象通常是对共享享元对象的组合对象
FlyWeightFactory:享元工厂,主要用来创建并管理共享的享元对象。
Client:客户端,主要工作时维持一个对FlyWeight的引用,计算或存储享元对象的外部状态,可以访问共享和不共享的FlyWeight对象
package flyweight;
//享元接口
public interface FlyWeight {
//示意操作,传入外部状态
public void operation(String extrinsicState);
}
package flyweight;
import java.util.HashMap;
import java.util.Map;
//享元工厂
public class FlyWeightFactory {
// 示意一下,缓存多个享元对象
Map<String,FlyWeight> map = new HashMap<String,FlyWeight>();
public FlyWeight getFlyWeight(String key){
// 先从缓存中查找,是否存在key对应的FlyWeight对象
FlyWeight f = map.get(key);
if(f==null){ //如果不存在,创建一个新的FlyWeight对象
f= new ConcreteFlyWeight(key);
map.put(key, f);
}
return f;
}
}
package flyweight;
//享元对象
public class ConcreteFlyWeight implements FlyWeight {
private String intrinsicState; // 内部状态
public ConcreteFlyWeight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
// 具体的功能处理,可能用到享元的内部、外部状态
}
}
package flyweight;
//不需要共享的享元对象,通常是将共享的数据进行组合而成的对象
public class UnsharedConcreteFlyWeight implements FlyWeight {
private String allState;
@Override
public void operation(String extrinsicState) {
// TODO Auto-generated method stub
}
}
使用享元模式解决权限问题;
分析:实际上重复出现的数据主要是对安全实体和权限的描述,又二者是不分开的,而且是可以重用的。
所以安全实体和权限描述定义成享元,而和它们结合的人员数据,就可以作为享元的外部数据。
在享元模式中,享元对象分为共享与不共享对象,通常共享的是叶子对象,不共享的部分是由共享部分组合而成的,由于所有细粒度的叶子对象都已经缓存了,缓存组合对象就没有必要了
工业级的实例池有两个难点:1,动态控制实例数量,2,动态分配实例给外部使用
在flyweightSolve包中的实例中,添加上不需共享的享元对象实现如包unsharedFlyweight
对享元对象的管理,所谓垃圾,就是在缓存中存在,但是不再需要被使用的缓存中的对象。
引用计数,就是要记录享元对象被引用的次数
包manageFlyweight中实现引用计数和垃圾清除