spring之旅第五篇-AOP详解
一、
aspect oritention programming(面向切面编程),aop是一种思想,高度概括的话是“横向重复,纵向抽取”,如何理解呢?举个例子:访问页面时需要权限认证,如果每个页面都去实现方法显然是不合适的,这个时候我们就可以利用切面编程。
每个页面都去实现这个方法就是横向的重复,我们直接从中切入,封装一个与主业务无关的权限验证的公共方法,这样可以减少系统的重复代码,降低模块之间的耦合度,简单的示意图如下:
二、应用场景
1.连接点(joinpoint) 所谓连接点是指那些可能被拦截到的方法。例如:所有可以增加的方法
2.切点(pointcut) 已经被增强的连接点
3.增强(advice) 增强的代码
4.目标对象(target) 目标类,需要被代理的类
5.织入(weaving) 是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程
6.代理(proxy) 一个类被aop织入增强后,就产生出了一个结果类,它是融合了原类和增强逻辑的代理类。
7.切面(aspect)切入点+通知
通知类型:spring按照通知advice在目标类方法的连接点位置,可以分为5类
-
-
后置通知(在目标方法执行后实施增强)
-
环绕通知(在目标方法执行前后实施增加)
-
异常抛出通知(在方法跑出异常时通知)
-
引介通知(在目标类中添加一些新的方法和属性)
四、实现原理
aop的实现关键在于aop框架自动创建的aop代理。aop代理主要分为两大类:
静态代理:使用aop框架提供的命令进行编译,从而在编译阶段就可以生成aop代理类,因此也称为编译时增强;静态代理一aspectj为代表。
动态代理:在运行时借助于jdk动态代理,cglib等在内存中临时生成aop动态代理类,因此也被称为运行时增强,spring aop用的就是动态代理。
4.1
//员工类 public class employee { private integer uid; public void setuid(integer uid) { this.uid = uid; } public integer getuid() { return uid; } private integer age; private string name; public integer getage() { return age; } public string getname() { return name; } public void setage(integer age) { this.age = age; } public void setname(string name) { this.name = name; } }
员工接口:
//员工接口 public interface employeeservice { //新增方法 void addemployee(employee employee); //删除方法 void deleteemployee(integer uid); }
员工实现:
//员工方法实现 public class employeeserviceimpl implements employeeservice { @override public void addemployee(employee employee) { system.out.println("新增员工"); } @override public void deleteemployee(integer uid) { system.out.println("删除员工"); } }
事务类:
//事务类 public class mytransaction { //开启事务 public void before(){ system.out.println("开启事务"); } //提交事务 public void after(){ system.out.println("提交事务"); } }
代理类:
//代理类 public class proxyemployee implements employeeservice { // private employeeservice employeeservice; private mytransaction mytransaction; public proxyemployee(employeeservice employeeservice,mytransaction mytransaction) { this.employeeservice=employeeservice; this.mytransaction=mytransaction; } @override public void addemployee(employee employee) { mytransaction.before(); employeeservice.addemployee(employee); mytransaction.after(); } @override public void deleteemployee(integer uid) { mytransaction.before(); employeeservice.deleteemployee(uid); mytransaction.after(); } }
测试:
@test public void fun1(){ mytransaction transaction = new mytransaction(); employeeservice employeeservice = new employeeserviceimpl(); //产生静态代理对象 proxyemployee proxy = new proxyemployee(employeeservice, transaction); proxy.addemployee(null); proxy.deleteemployee(0); }
结果:
这是静态代理的实现方式,静态代理有明显的缺点:
1、代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2、如果接口增加一个方法,比如 employeeservice增加修改 updateemployee()方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
4.2
public class objectinterceptor implements invocationhandler { //目标类 private object target; //切面类(这里指事务类) private mytransaction transaction; //通过构造器赋值 public objectinterceptor(object target,mytransaction transaction){ this.target = target; this.transaction = transaction; } @override public object invoke(object proxy, method method, object[] args) throws throwable { this.transaction.before(); method.invoke(target,args); this.transaction.after(); return null; } }
测试:
@test public void fun2(){ //目标类 object target = new employeeserviceimpl (); //事务类 mytransaction transaction = new mytransaction(); objectinterceptor proxyobject = new objectinterceptor(target, transaction); /** * 三个参数的含义: * 1、目标类的类加载器 * 2、目标类所有实现的接口 * 3、拦截器 */ employeeservice employeeservice = (employeeservice) proxy.newproxyinstance(target.getclass().getclassloader(), target.getclass().getinterfaces(), proxyobject); employeeservice.addemployee(null); employeeservice.deleteemployee(0); }
结果:
五、
<?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--目标类--> <bean name="employeeservice" class="com.yuanqinnan.aop.employeeserviceimpl"></bean> <bean name="transaction" class="com.yuanqinnan.aop.mytransaction"></bean> <aop:config> <aop:aspect ref="transaction"> <aop:pointcut id="pointcut" expression="execution(* com.yuanqinnan.aop.employeeserviceimpl..*(..))"/> <!-- 配置前置通知,注意 method 的值要和 对应切面的类方法名称相同 --> <aop:before method="before" pointcut-ref="pointcut"></aop:before> <!--配置后置通知,注意 method 的值要和 对应切面的类方法名称相同--> <aop:after-returning method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
测试:
@test public void fun3(){ applicationcontext context = new classpathxmlapplicationcontext("meta-inf/applicationcontext.xml"); employeeservice employeeservice = (employeeservice) context.getbean("employeeservice"); employeeservice.addemployee(null); }
结果:
补充:
1.如果位于元素中,则命名切点只能被当前内定义的元素访问到,为了能被整个元素中定义的所有增强访问,则必须在下定义切点。
2.如果在元素下直接定义,必须保证在之前定义。下还可以定义,三者在中的配置有先后顺序的要求:首先必须是,然后是,最后是。而在中定义的则没有先后顺序的要求,可以在任何位置定义。
:用来定义切入点,该切入点可以重用;
:用来定义只有一个通知和一个切入点的切面;
:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。
3.在使用spring框架配置aop的时候,不管是通过xml配置文件还是注解的方式都需要定义pointcut"切入点" 例如定义切入点表达式 execution(* com.sample.service.impl...(..)) execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分: (1)、execution(): 表达式主体。
(2)、第一个号:表示返回类型,
5.2
@component @aspect public class aopaspectj { /** * 必须为final string类型的,注解里要使用的变量只能是静态常量类型的 */ public static final string edp="execution(* com.yuanqinnan.aop.employeeserviceimpl..*(..))"; /** * 切面的前置方法 即方法执行前拦截到的方法 * 在目标方法执行之前的通知 * @param jp */ @before(edp) public void dobefore(joinpoint jp){ system.out.println("=========执行前置通知=========="); } /** * 在方法正常执行通过之后执行的通知叫做返回通知 * 可以返回到方法的返回值 在注解后加入returning * @param jp * @param result */ @afterreturning(value=edp,returning="result") public void doafterreturning(joinpoint jp,string result){ system.out.println("===========执行后置通知============"); } /** * 最终通知:目标方法调用之后执行的通知(无论目标方法是否出现异常均执行) * @param jp */ @after(value=edp) public void doafter(joinpoint jp){ system.out.println("===========执行最终通知============"); } /** * 环绕通知:目标方法调用前后执行的通知,可以在方法调用前后完成自定义的行为。 * @param pjp * @return * @throws throwable */ @around(edp) public object doaround(proceedingjoinpoint pjp) throws throwable{ system.out.println("======执行环绕通知开始========="); // 调用方法的参数 object[] args = pjp.getargs(); // 调用的方法名 string method = pjp.getsignature().getname(); // 获取目标对象 object target = pjp.gettarget(); // 执行完方法的返回值 // 调用proceed()方法,就会触发切入点方法执行 object result=pjp.proceed(); system.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result); system.out.println("======执行环绕通知结束========="); return result; } /** * 在目标方法非正常执行完成, 抛出异常的时候会走此方法 * @param jp * @param ex */ @afterthrowing(value=edp,throwing="ex") public void doafterthrowing(joinpoint jp,exception ex) { system.out.println("===========执行异常通知============"); } }
xml配置
<?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.yuanqinnan.aop" ></context:component-scan> <!-- 声明spring对@aspectj的支持 --> <aop:aspectj-autoproxy/> <!--目标类--> <bean name="employeeservice" class="com.yuanqinnan.aop.employeeserviceimpl"></bean> </beans>
测试:
@test public void fun4(){ applicationcontext act = new classpathxmlapplicationcontext("meta-inf/applicationcontext.xml"); employeeservice employeeservice = (employeeservice) act.getbean("employeeservice"); employeeservice.addemployee(null); }
结果:
pringaop的知识就总结到这里
上一篇: vue里swiper的一些坑