什么是AOP(Aspect OrientedProgramming)
AOP是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。
AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP能干什么,也是AOP带来的好处
1:降低模块的耦合度
2:使系统容易扩展
3:更好的代码复用性
常用的开发模式,都是以竖行考虑开发的
但有些业务需要横向的需求
当项目要对每一步的操作进行一个日志的记录,权限的审查时,则是一个横向的关注点,如果在每一个方法中都写上一段几乎相同的代码,那就太冗余了,所以可以将日志记录,权限审查的模块提取出来
AOP中的概念
Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面是横切性关注点的抽象.
joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)
Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义.
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知
Target(目标对象):代理的目标对象
Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入.
Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
AOP中类及方法的注解
Aspect(切面):通过@Aspect来对拦截的自定义类进行注解,用于实现AOP.
Pointcut(切入点):切入点是在自定义类中进行声明的个私有的方法,使用@Pointcut进行注解,如: @Pointcut("execution(*biz.impl.PersonBizImpl.*(..))").
前置通知:使用: @Before(“切入点的方法名称() && args(参数名)”)进行注解
后置通知:使用: @AfterReturning(pointcut = “切入点的方法名称()”, returning = “参数名")进行注解,返回值为拦截方法的返回值
例外通知:使用: @AfterThrowing("切入点的方法名称()")进行注解
最终通知:使用: @After(“切入点的方法名称()”)进行注解
环绕通知:使用: @Around(“切入点的方法名称() ”)进行注解
权限拦截类
实现权限拦截的同时,记录操作日志
package interceptor; //省略了导入的库包 import util.BeansUtil; //获取bean对象的工具类 import util.UserUtil; //初始化用户的工具类 import biz.SysLogBiz; //日志业务操作类 import entity.SysLog; //日志实体类 import entity.UserInfo; //用户实体类 @Aspect @Component public class PowerInterceptor { // 第一个* 代表任意的返回类型 (..) 所有参数 @Pointcut("execution(* action.*.*(..))") private void anyMethod() { }; //声明一个切入点,自定义一个私有的方法 @Before("anyMethod() && args()") public void doBefore() { System.out.println("前置通知"); } @AfterReturning(pointcut = "anyMethod()", returning = "name") public void doAfterReturning(String name) { System.out.println("后置通知:"+name); } @AfterThrowing("anyMethod()") public void doAfterThrowing() { System.out.println("例外通知"); } @After("anyMethod()") public void doAfter() { System.out.println("最终通知"); } @Around("anyMethod()") //环绕通知 public Object doAround(ProceedingJoinPoint pjp) throws Throwable { // 要执行pip.proceed方法 Object obj = null; //初始化用户信息,如果在web项目中是通过session回话中获取当前登入的用户 UserInfo user = UserUtil.getCurrentUser(); //通过pjp对象可以获取到需要操作的类和类中的方法 String className = pjp.getSignature().getDeclaringTypeName(); System.out.println(className); //打印操作的类,结果为action.DeptAction //将获取到的action.DeptAction,转化为deptAction className = className.substring(className.lastIndexOf(".") + 1); className = className.substring(0, 1).toLowerCase() + className.substring(1); if (UserUtil.checkPower(className, user.getPower())){ //判断用户的权限 try { System.out.println("有权限"); obj = pjp.proceed();// 允许用户接着调用目标的方法 SysLog log = new SysLog(); log.setLogId(1); log.setLogMsg(BeansUtil.getLogMsg(className,pjp.getSignature() .getName()) + ",操作成功!"); log.setLogTime(new Date()); // 获取日志操作 BEAN SysLogBiz logbiz = (SysLogBiz) BeansUtil .getBean("sysLogBizImpl"); logbiz.addLog(log); //保存日志信息 System.out.println(log.getLogMsg()); } catch (Exception e) { SysLog log = new SysLog(); log.setLogId(1); log.setLogMsg(BeansUtil.getLogMsg(className,pjp.getSignature() .getName()) + ",操作失败!"); log.setLogTime(new Date()); // 获取日志操作 BEAN SysLogBiz logbiz = (SysLogBiz) BeansUtil .getBean("sysLogBizImpl"); logbiz.addLog(log); System.out.println(log.getLogMsg()); } } else { System.out.println("你没有权限。"); } return obj; } }
测试类:
package test; import util.BeansUtil; import action.DeptAction; public class PowerTest { /** * @param args */ public static void main(String[] args) { // UserAction action =(UserAction) BeansUtil.getBean("userAction"); // action.add(); DeptAction action = (DeptAction) BeansUtil.getBean("deptAction"); action.add(); //调用DeptAction的添加方法 } }
测试结果为:
结果中后置通知的:ok22是DeptAction中的返回方法
DeptActon中的add方法就返回了ok22操作
Spring提供了两种切面使用方式
Spring提供了两种切面使用方式,实际工作中我们可以选用其中一种:
基于XML配置方式进行AOP开发。
基于注解方式进行AOP开发。
上面介绍的是使用注解方式开发,在使用这种方式开发的时候还需要在spring配置文件中加配置
<aop:aspectj-autoproxyproxy-target-class="true"/>
基于XML配置方式使用切面
自定义类
public classLogPrint { public void doAccessCheck() {}定义前置通知 public void doReturnCheck() {}定义后置通知 public void doExceptionAction() {}定义例外通知 public void doReleaseAction() {}定义最终通知 public ObjectdoBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { return pjp.proceed();环绕通知 } }
配置文件的配置
<bean id="orderservice"class="cn.itcast.service.OrderServiceBean"/> <bean id="log"class="cn.itcast.service.LogPrint"/> <aop:config> <aop:aspect id="myaop"ref="log"> <aop:pointcut id="mycut" expression="execution(*cn.itcast.service..*.*(..))"/> <aop:before pointcut-ref="mycut" method="doAccessCheck"/> <aop:after-returning pointcut-ref="mycut" method="doReturnCheck"/> <aop:after-throwing pointcut-ref="mycut" method="doExceptionAction"/> <aop:after pointcut-ref="mycut" method=“doReleaseAction"/> <aop:around pointcut-ref="mycut" method="doBasicProfiling"/> </aop:aspect> </aop:config>
指示符
expression-匹配方法执行的连接点,是Spring最主要的切入点
execution(modifiers-pattern?ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
除了喊会类型模式(),名字模式和参数模式以外,其余的都是可选的,*为任意通配符
expression=“execution(*cn.itcast.service..*.*(..))”
表示作用在cn.itcast.service包及子包下所有的类的所有方法上
expression=“execution(*cn.itcast.service..*.*(java.lang.String,..))”
表示作用在cn.itcast.service包及子包下所有的类的第一个参数为String类型的方法上
expression=“execution(java.lang.Stringcn.itcast.service..*.*(..))”
表示作用在cn.itcast.service包及子包下所有的类的返回值类型为String的方法上
expression=“execution(!java.lang.Stringcn.itcast.service..*.*(..))”
表示作用在cn.itcast.service包及子包下所有的类的返回值类型不是String的所有方法上
expression=“execution(* set**(..))”
表示作用在任意以set开始的方法上
within-限定匹配特定类型的连接点
expression=“winthin(cn.itcast.*)”
表示作用在cn.itcast包及子包下的所有连接点