第一步:
注解和xml一样只是起到一个配置的作用。注解的本身是不可能完成这种拦截功能的。我们后面会通过注册一个驱动了去解析这个注解,完成这个注解背后所代表的功能。
新建注解:
public @interface Permission {
}
我们这个项目是通过两个属性来代表这个权限的,所以我们这里也需要两个属性。
public @interface Permission {
String module();
String privilege();
}
标注@Retention
通过这个注解来表标注:这个注解的配置在这个java类中的存放的范围,比如:
@Retention(RetentionPolicy.SOURCE)
只能保留在源代码上面,一但被编译成class之后这个注解就会丢失
@Retention(RetentionPolicy.CLASS)
在源代码中和编译过后的class中都回存在这个注解,但是当这个类被装载进java虚拟机后,这个注解就会丢失了
@Retention(RetentionPolicy.RUNTIME)
代表这个注解要保留至运行期
所以我们这里一定要标注为运行期
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
String module();
String privilege();
}
@Target
用来标识这个注解他可以标注在什么地方。
@Target({ElementType.FIELD})
可以标注在字段上面
@Target({ElementType.FIELD,ElementType.METHOD})
也可以标注在方法上面
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
也可以标注在类型上面
我们这里只需标注在方法上面就可以了:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Permission {
String module();
String privilege();
}
那么我们就开始第二步应用了:
比如在部门管理action里面:
(我们前面初始化的时候就初始化了一些权限,那么我们就可以把初始化的数据拷贝过来)
/**
* 部门管理
*/
@Controller("/control/department/manage")
public class DepartmentManageAction extends DispatchAction {
@Resource DepartmentService departmentService;
/**
* 部门添加界面
*/
@Permission(module="department",privilege="insert")
public ActionForward addDepartmentUI(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throwsException {
DepartmentForm formbean = (DepartmentForm)form;
return mapping.findForward("add");
}
。。。。。。。。
这样我们就标注上了这个方法需要department模块里面的insert权限。这样就可以细粒度的表面,用户想要执行这个方法就需要这个方式上面标注的权限。
第三步:
我们最关键的步骤,那么我们如何当用户执行这个方法的之前拦截,来判断他是否具有这个权限。如果没有权限就让他回到一个提示页面。首先要获取他的注解,
那么我们会想到使用aop切面编程:因为我们需要方法执行前需要做一个权限判断工作
通知:拦截到这个方法调用的时候需要执行的工作。
对所有的action方法进行拦截 –> 切入点
其中的一个方法 à 连接点
切面编程 à 我们对横切关注点
Aop步骤:拦截action à 获取方法上的权限注解 à 判断登陆的用户是否有用该注解 à 如果存在就允许执行 ;否则提示没有权限界面
环绕通知:around
前绕通知:如果前面执行没抛出以外,后面的方法还是会正常执行。
首先我们要确定是否已经被spring进行管理才能用aop,目前我们的action都已经被spring进行管理了。
定义切面的配置方式:
一:xml
二:注解
我们定义一个类,加上注解把他定义为切面。
@Aspect
@Component
public class Interceptor {
@Pointcut("execution(org.apache.struts.action.ActionForward cn.itcast.web.action..*.*(org.apache.struts.action.ActionMapping,org.apache.struts.action.ActionMapping,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse))")
private void actionMethod(){
}
@Around("actionMethod()")
public Object intercept(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("被拦截到的方法为:"+ pjp.getSignature().getName());
return pjp.proceed();//执行被拦截的方法
}
}
我们可以这样测试,有没有拦截到我们要执行的方法。
测试结果:execute方法可以被拦截,自定义方法无法拦截。如果继承的是action,可以被拦截到,如果继承的是dispatchAction,不能被拦截到
原因:简单说,如果是通过反射技术调用的方法不能拦截到,否则就可以被拦截。
那么为什么反射技术就不能被拦截到?
之所以我们在执行action执行,他能够被拦截,执行之前输出一句话,客户端从spring里面获得action bean是一个代理对象,代理对象中的方法内部呢:才会调用目标对象的方法,他会在执行代理对象之前呢,先执行输出方法,再执行目标方法,那么我们这样就在执行之前输出了这句话。
DispatchAction 继承 BaseAction ,BaseAction 继承 Action ,最终还是继承的Action,那么我们请求交给DispatchAction 执行时,他还是要执行execute方法,我们的DispatchAction对execute方法做了些手脚,重写了这个方法,他会获得配置文件里面的方法名称parameter,这个执行获取方法会通过反射技术获得method里面的方法。他里面没有接口,如果这个action没有接口,就会采用CGlib重写父类非final方法,
public class DepartmentManageAction extends DispatchAction {
@Resource DepartmentService departmentService;
/**
* 部门添加界面
*/
@Permission(module="department",privilege="insert")
public ActionForward addDepartmentUI(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throwsException {
DepartmentForm formbean = (DepartmentForm)form;
return mapping.findForward("add");
}
Public class Proxy98 extends DepartmentManageAction{
Private Object target;//目标对象
public ActionForward addDepartmentUI(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throwsException {
他会调用我们定义的那个通知里面的环绕通知
Return Interceptor。Intercept(pjp){
System.out.println("被拦截到的方法为:"+ pjp.getSignature().getName());
return pjp.proceed();//执行被拦截的方法
判断后面还有没有要执行的切面,没有就会执行
Targer。EditDepartmentmentUi;
我们这样开来,他应该回输出啊,会执行我们定义的输出方法啊,但是你要记住我们无乱使用的是action还是DispatchAction都回先执行execute方法。Spring不会为代理对象重写的父类方法加入通知,他只会调用目标对象的execute方法。
}
}
}
总结:为什么DispatchAction不可以
我们环绕通知切面编程原理,如果要拦截的对象没有实现接口,那么就会对他的内部方法也就目标对象生成代理对象(也就是加入通知),但是我们他不会对被拦截的类的父类方法加入通知,而我们的DispatchAction方法最终还是集成的Action方法,所以会先执行execute方法,execute方法所以不会加入通知,只会直接执行目标对象execute方法,而我们DispatchAction方法重写了execute方法,execute内部会通过invoke反射调用我们的目标方法,也就是上面的addDepartmentUI,我们目标对象addDepartmentUI方法内部本身并没有我们单独加上的处理(比如上面的输出),也就是说真实的执行过程,压根就没调用我们的代理对象,而是直接走的没有加入通知的execute方法。
正确的解决思路:
必须要对spring代理对象的execute方法进行改造,不建议改写。
我们最终的目的是拦截action的方法,我们可以自己来实现aop功能:
继承requestProssor(请求处理器),实现对action的方法进行拦截。
RequestProcessor:
RequestProcessor原理:当一个请求到来时,他会先交给RequestProcessor对象,RequestProcessor会根绝用户的请求路径找到匹配的action,再把请求交给对应的action进行处理。我们就可以在调用action之前做一下权限校验。
RequestProcessor内部具体操作:
首先%3