AnnotationHandler

本文介绍如何利用Annotation简化权限控制,并通过动态代理、反射实现Dao层方法与权限检查的解耦。重点阐述了如何为每个用户设置权限、如何在不同线程下进行操作以及如何通过Annotation实现权限信息的注入。

下面我们就用一个权限控制的例子来说明一下,如何使用Annotation来简化我们的开发。

预期功能:

1. 对于每个用户都设定一个对应的权限。

2. 每个Dao的操作都加入对权限的检查。权限不足则抛出安全异常。

思考:

1. Dao层的方法只关心Dao的操作,对于权限的检查则不需要关心。因此我们可以用AOP来实现对权限的检查(在Java中使用动态代理来实现),实现权限检查和Dao操作的解耦。

2. 每个用户都要有相应的权限,而且每个用户的操作都是在不同的线程上进行,因为我们必须要提供一个用户的权限上下文(RoleContext)来提供对权限的设置和获取。

3. 对于Dao层的实现可以采用面向接口的编码方式,实现各层之间的解耦。由于每个Dao层所对应的实现类只有一个,因此,我们可以把实现类的信息作为元数据写入Dao接口中,所以这里最适合用Annotation来实现。

4. Dao层的方法所需要的权限信息与实现无关,因此这里也可以把权限的信息作为方法的元数据写入,所以这里也十分适合用Annotation来实现。

Part IV

首先我们把项目基本的架子搭建:

package com.gzmu.annotation.dao;
public interface BaseDao { }
package com.gzmu.annotation.dao;
import com.gzmu.annotation.annotation.Implement;
import com.gzmu.annotation.annotation.Permission;
import com.gzmu.annotation.dao.impl.UserDaoImpl;
import com.gzmu.annotation.util.Role;
@Implement(UserDaoImpl.class)
public interface UserDao extends BaseDao {
	
	@Permission({Role.ADMINISTRATOR, Role.SYSTEM})
	void save();
	
	@Permission(Role.SYSTEM)
	void delete();
	
	@Permission({Role.USER, Role.ADMINISTRATOR, Role.SYSTEM})
	void query();
	
}
package com.gzmu.annotation.dao.impl;
import com.gzmu.annotation.dao.UserDao;
public class UserDaoImpl implements UserDao {
	
	@Override
	public void save() {
		System.out.println("UserDaoImpl.save()");
	}
	
	@Override
	public void delete() {
		System.out.println("UserDaoImpl.delete()");
	}
	
	@Override
	public void query() {
		System.out.println("UserDaoImpl.query()");
	}
	
}

RoleContext作为一个提供用户权限上下文的单元存在,使用枚举来实现单例模式,ThreadLocal提供了对当前线程权限数据的访问。

package com.gzmu.annotation.context;
import com.gzmu.annotation.util.Role;
public enum RoleContext {
	
	INSTANCE;
	
	private ThreadLocal<Role> role = new ThreadLocal<Role>();
	
	public Role getCurrentRole() {
		return role.get();
	}
	
	public void setCurrentRole(Role role) {
		this.role.set(role);
	}
	
}

Implment用来指定Dao接口对应的实现类。

package com.gzmu.annotation.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.gzmu.annotation.dao.BaseDao;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Implement {
	
	Class<? extends BaseDao> value();
	
}

Permission用于指定Dao层的方法的可访问的人员的访问权限。

package com.gzmu.annotation.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.gzmu.annotation.util.Role;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
	
	Role[] value();
	
}

到这里,这个基本的架子就搭建完成了。接下来,我们就要开始使用动态代理、反射以及Annotation来实现对权限的检查。

Part V

下面我们就要详细的解释一下以下的代码:

DaoProxyFactory.newRoleDaoProxy():

1. 我们提供一个简单的工厂,用于生产一个代理对象。传入一个需要代理的接口,用于产生实现该接口的代理对象。

2. 由于我们的接口上使用Implement这个Annotation来指定这个接口所对应的实现类,所以我们可以获取这个实现类会创建一个实际被代理的对象。

RoleInvocationHandler

1. 顾名思义,这个类就是用来做权限控制的,这个类实现了InvocationHandler。

2. 因为我们已经在接口上定义了哪些方法对应哪些被允许执行这个方法的权限,因此我们可以通过method.getAnnotation(Permission.class)这个方法来获得权限的信息。

3. 迭代方法的允许权限,并与当前线程用户的权限做比较,如果发现两者相等,说明当前用户的权限与方法执行的权限一致,因此跳出循环,执行outter标签后面的方法,允许用户执行。

4. 迭代完成后,当前线程用户的权限没有与方法中定义的权限一致,说明用户无权执行这样的操作,因此跑出安全异常。

package com.gzmu.annotation.util;
import java.lang.annotation.AnnotationFormatError;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.gzmu.annotation.annotation.Implement;
import com.gzmu.annotation.annotation.Permission;
import com.gzmu.annotation.context.RoleContext;
import com.gzmu.annotation.dao.BaseDao;
public abstract class DaoProxyFactory {
	
	@SuppressWarnings("unchecked")
	public static <T> T newRoleDaoProxy(Class<T> dao) {
		Implement implAnnotation = dao.getAnnotation(Implement.class);
		
		if (implAnnotation == null)
			throw new AnnotationFormatError("该接口未定义实现类的注解");
		
		BaseDao implClass = null;
		try {
			implClass = implAnnotation.value().newInstance();
		} catch (Exception e) {
			throw new RuntimeException("该接口所定义的实现类不能被实例化", e);
		}
		
		return (T) Proxy.newProxyInstance(
				DaoProxyFactory.class.getClassLoader(),
				new Class<?>[] { dao },
				new RoleInvocationHandler(implClass)
		);
	}
	
	private static final class RoleInvocationHandler implements InvocationHandler {
		private BaseDao target;
		
		public RoleInvocationHandler(BaseDao target) {
			this.target = target;
		}
		
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			Permission permitAnnotation = method.getAnnotation(Permission.class);
			
			outter:
			if (permitAnnotation != null) {
				Role currentRole = RoleContext.INSTANCE.getCurrentRole();
				for (Role permitRole : permitAnnotation.value()) {
					if (permitRole.equals(currentRole))
						break outter;
				}
				throw new SecurityException("当前用户不允许执行此操作");
			}
			
			return method.invoke(target, args);
		}
		
	}
	
}
如何用偌依框架搭建项目 14:16:51.318 [restartedMain] ERROR o.s.b.SpringApplication - [reportFailure,870] - Application run failed org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'captchaController': Unsatisfied dependency expressed through field 'configService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sysConfigServiceImpl': Unsatisfied dependency expressed through field 'configMapper'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sysConfigMapper' defined in file [D:\develop\RuoYi-Vue-master-depute\RuoYi-Vue-master\ruoyi-system\target\classes\com\ruoyi\system\mapper\SysConfigMapper.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: com/baomidou/mybatisplus/core/handlers/AnnotationHandler at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:713) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:693) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:408) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1431) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:209) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:591) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:453) at org.springframework.boot.SpringApplication.run(SpringApplication.java:343) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1370) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1359) at com.ruoyi.RuoYiApplication.main(RuoYiApplication.java:23) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sysConfigServiceImpl': Unsatisfied dependency expressed through field 'configMapper'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sysConfigMapper' defined in file [D:\develop\RuoYi-Vue-master-depute\RuoYi-Vue-master\ruoyi-system\target\classes\com\ruoyi\system\mapper\SysConfigMapper.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: com/baomidou/mybatisplus/core/handlers/AnnotationHandler at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:713) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:693) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:408) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1431) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:209) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1391) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:710) ... 25 common frames omitted Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sysConfigMapper' defined in file [D:\develop\RuoYi-Vue-master-depute\RuoYi-Vue-master\ruoyi-system\target\classes\com\ruoyi\system\mapper\SysConfigMapper.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: com/baomidou/mybatisplus/core/handlers/AnnotationHandler at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1534) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1417) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:209) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1391) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:710) ... 39 common frames omitted Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: com/baomidou/mybatisplus/core/handlers/AnnotationHandler at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:646) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:626) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:209) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1391) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1519) ... 50 common frames omitted Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: com/baomidou/mybatisplus/core/handlers/AnnotationHandler at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:641) ... 63 common frames omitted Caused by: java.lang.NoClassDefFoundError: com/baomidou/mybatisplus/core/handlers/AnnotationHandler at com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration.sqlSessionFactory(MybatisPlusAutoConfiguration.java:218) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ... 64 common frames omitted Caused by: java.lang.ClassNotFoundException: com.baomidou.mybatisplus.core.handlers.AnnotationHandler at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) ... 70 common frames omitted
最新发布
11-11
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值