实现之前,先简单介绍下业务:
访问控制列表ACT(Access Control List),当访问者访问资源时,对访问的权限进行判断是否可以访问:
1)权限级别: Boss(老板);Manager(经理);Accountant(会计);Employee(员工);
2)资源文件: Achievement Document(业务统计文件);Salary Document(薪酬信息文件);
Public Document(公共文件);
3)控制逻辑: I:老板可以访问任何文件;
II:经理可以访问员工的业务统计文件;
III:会计可以访问任何人的薪酬信息文件;
IV:非老板和会计无法访问别人的薪酬信息文件,只可以访问属于自己的薪酬信息文件;
V: 非老板和经理无法访问别人的业务统计文件,只可以访问属于自己的业务统计文件;
VI: 公共文件任何人都可以访问;
代码:
首先写出权限级别的枚举、注解和实例
public enum PersonLevel{ Boss, Manager, Employee, Accountant,; }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Level { PersonLevel level() default PersonLevel.Employee; }
public class Personnel { private String name; public Personnel(String name) { this.name = name; } public String getName() { return name; } public void access(Document document){ Level level = this.getClass().getAnnotation(Level.class); boolean access = level.level().access(this, document); if(access){ document.canAccess(); }else { document.canNotAccess(); } } } @Level(level = PersonLevel.Boss) class Boss extends Personnel{ public Boss(String name) { super(name); } } @Level(level = PersonLevel.Manager) class Manager extends Personnel{ public Manager(String name) { super(name); } } @Level() class Employee extends Personnel{ public Employee(String name) { super(name); } } @Level(level = PersonLevel.Accountant) class Accountant extends Personnel{ public Accountant(String name) { super(name); } }
员工四个权限级别实例,员工内部有私有属性name表示员工名字,员工内部方法access用来访问文件;
访问方法先通过反射获取访问者的权限级别注解,在调用权限级别的控制逻辑判断访问者是否有权限访问该文件:
如果有权限则调用文件canAccess方法展示文件内容;
如果没有权限则调用文件canNotAccess方法提示访问者;
所以要改造一下级别枚举PersonLevel为其添加控制逻辑:
public enum PersonLevel{ Boss{ @Override boolean access(Personnel personnel, Document document) { return true; } }, Manager { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || category == SourceCategory.Achievement || personnel == document.getPersonnel(); } }, Employee { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || personnel == document.getPersonnel(); } }, Accountant { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || category == SourceCategory.Salary || personnel == document.getPersonnel(); } },; abstract boolean access(Personnel personnel, Document document); }
接下来在写出文件的类型枚举、注解和实例
public enum SourceCategory{ // 薪酬信息, 业务统计, 公共信息 Salary, Achievement, Public,; }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Source { SourceCategory category() default SourceCategory.Public; }
public class Document { private Personnel personnel; private String title; private String content; public Document(Personnel personnel, String title, String content) { this.personnel = personnel; this.title = title; this.content = content; } public void canNotAccess(){ System.out.println("抱歉,对于<"+title+">您没有访问权限!"); } public void canAccess(){ System.out.println("<"+title+">文件内容:"+content); } public Personnel getPersonnel() { return personnel; } } @Source() class PublicDocument extends Document{ public PublicDocument(Personnel personnel, String title, String content) { super(personnel, title, content); } } @Source(category = SourceCategory.Salary) class SalaryDocument extends Document{ public SalaryDocument(Personnel personnel, String title, String content) { super(personnel, title, content); } } @Source(category = SourceCategory.Achievement) class AchievementDocument extends Document{ public AchievementDocument(Personnel personnel, String title, String content) { super(personnel, title, content); } }
文件有内部私有属性Personnal(如文件类型为薪酬信息或业务统计则表示信息的所有者)、title(文件名称)和content(文件内容),并实现了canAccess(访问文件)和canNotAccess(无权限提示)方法;
接下来我们写个demo来测试下我们的代码:
public class Client { public static void main(String[] args) { List<Personnel> plist = new ArrayList<>(); Personnel boss = new Boss("boss"); Personnel manager = new Manager("manager"); Personnel zhangsan = new Employee("zhangsan"); Personnel lisi = new Employee("lisi"); Personnel accountant = new Accountant("accountant"); plist.add(boss); plist.add(manager); plist.add(zhangsan); plist.add(lisi); plist.add(accountant); List<Document> dlist = new ArrayList<>(); Document publicDocument = new PublicDocument(null, "十月一日放假公告", "十月一日放三天假"); Document managerSalary = new SalaryDocument(manager, "经理工资单", "工资30000/月"); Document zhangsanSalary = new SalaryDocument(zhangsan, "zhangsan工资单", "工资12000/月"); Document lisiSalary = new SalaryDocument(lisi, "lisi工资单", "工资8000/月"); Document accountantSalary = new SalaryDocument(accountant, "accountant工资单", "工资8000/月"); Document zhangsanAchievement = new AchievementDocument(zhangsan, "zhangsan业绩", "200单/月"); Document lisiAchievement = new AchievementDocument(lisi, "lisi业绩", "100单/月"); dlist.add(publicDocument); dlist.add(managerSalary); dlist.add(zhangsanSalary); dlist.add(lisiSalary); dlist.add(accountantSalary); dlist.add(zhangsanAchievement); dlist.add(lisiAchievement); for (Personnel personnel : plist) { System.out.println("当前访问人:"+personnel.getName()); for (Document document : dlist) { personnel.access(document); } System.out.println("==================================="); } } }
先创建五个不同级别的访问者,以及三种不同类型的共计7个文件;
运行结果:
当前访问人:boss
<十月一日放假公告>文件内容:十月一日放三天假
<经理工资单>文件内容:工资30000/月
<zhangsan工资单>文件内容:工资12000/月
<lisi工资单>文件内容:工资8000/月
<accountant工资单>文件内容:工资8000/月
<zhangsan业绩>文件内容:200单/月
<lisi业绩>文件内容:100单/月
===================================
当前访问人:manager
<十月一日放假公告>文件内容:十月一日放三天假
<经理工资单>文件内容:工资30000/月
抱歉,对于<zhangsan工资单>您没有访问权限!
抱歉,对于<lisi工资单>您没有访问权限!
抱歉,对于<accountant工资单>您没有访问权限!
<zhangsan业绩>文件内容:200单/月
<lisi业绩>文件内容:100单/月
===================================
当前访问人:zhangsan
<十月一日放假公告>文件内容:十月一日放三天假
抱歉,对于<经理工资单>您没有访问权限!
<zhangsan工资单>文件内容:工资12000/月
抱歉,对于<lisi工资单>您没有访问权限!
抱歉,对于<accountant工资单>您没有访问权限!
<zhangsan业绩>文件内容:200单/月
抱歉,对于<lisi业绩>您没有访问权限!
===================================
当前访问人:lisi
<十月一日放假公告>文件内容:十月一日放三天假
抱歉,对于<经理工资单>您没有访问权限!
抱歉,对于<zhangsan工资单>您没有访问权限!
<lisi工资单>文件内容:工资8000/月
抱歉,对于<accountant工资单>您没有访问权限!
抱歉,对于<zhangsan业绩>您没有访问权限!
<lisi业绩>文件内容:100单/月
===================================
当前访问人:accountant
<十月一日放假公告>文件内容:十月一日放三天假
<经理工资单>文件内容:工资30000/月
<zhangsan工资单>文件内容:工资12000/月
<lisi工资单>文件内容:工资8000/月
<accountant工资单>文件内容:工资8000/月
抱歉,对于<zhangsan业绩>您没有访问权限!
抱歉,对于<lisi业绩>您没有访问权限!
===================================
所得结果符合我们的预期!
在开发过程中,遇见了两个问题:
1.开始的时候并未编写访问者(Personnal)和文件(Document)的实现类而之编写了父类
注解的类型并非使用@Target(ElementType.TYPE)而是@Target(ElementType.LOCAL_VARIABLE)
然后在demo中直接在创建实例的时候注解访问者的权限级别和文件类型
@Level(level = PersonLevel.Boss) Personnel boss = new Boss("boss");
但是这种在运行时报java.lang.NullPointerException
原因是在调用访问者的access访问来访问文件时,通过反射
Level level = this.getClass().getAnnotation(Level.class);
获取的变量访问权限级别注解为null
而导致在调用的注解level()方法获取注解值:访问权限实例(理论上为PersonLevel.Boss)候抛出空指针异常:
原因是:
目前的javac不会在bytecode中的local variable中保存annotation信息,所以就无法在runtime时获取该annotaion。也就是说ElementType.LOCAL_VARIABLE只能用在RetentionPolicy.SOURCE情况下。
2.开始的时候我是把注解和枚举写在一起的
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Level { PersonLevel level() default PersonLevel.Employee; } enum PersonLevel{ Boss{ @Override boolean access(Personnel personnel, Document document) { return true; } }, Manager { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || category == SourceCategory.Achievement || personnel == document.getPersonnel(); } }, Employee { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || personnel == document.getPersonnel(); } }, Accountant { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || category == SourceCategory.Salary || personnel == document.getPersonnel(); } },; abstract boolean access(Personnel personnel, Document document); }
这样在运行时会报错:
java.lang.IllegalAccessError: tried to access class package.PersonLevel from class com.sun.proxy.$Proxy1
代理类访问不了PersonLevel,注解调用level()获取访问者权限枚举的时候获取不到,后来我把枚举提出来变成public访问修饰符此问题就解决了,多次测试发现,枚举写在注解类内部或者访问修饰符是public才可以,这个什么原因希望有了解的大神可以指正!
本片文章就到这里了,这是本人第一次分享编程知识,如有任何问题都希望能与本人交流指正。