网站开发多少工资,做网站要排版吗,个人网站备案地址,知名网站的org域名Spring AOP与AspectJ
概念
AOP的全称为Aspect-Oriented Programming#xff0c;即面向切面编程。
想象你是汉堡店的厨师#xff0c;每一份汉堡都有好几层#xff0c;这每一层都可以视作一个切面。现在有一位顾客想要品尝到不同风味肉馅的汉堡#xff0c;如果按照传统的方…Spring AOP与AspectJ
概念
AOP的全称为Aspect-Oriented Programming即面向切面编程。
想象你是汉堡店的厨师每一份汉堡都有好几层这每一层都可以视作一个切面。现在有一位顾客想要品尝到不同风味肉馅的汉堡如果按照传统的方式你需要做多个汉堡每个汉堡只有肉馅是不一样的但是你每做一个汉堡都要重新制作面包。而聪明的厨师只需做一个汉堡仅将肉饼那一层分成不同口味的几个区域这样你就不需要再重复制作面包了。
对于程序员也是一样的有多少个接口就要写或复制多少代码那一定是无法忍受的我们只想关心不同的那部分。
尽管想通俗来讲但是还是要去熟悉专业的概念
Aspect切面类似于Java类声明里面会有Pointcut和AdviceJoint point连接点在程序执行过程中某个阶段点Pointcut切入点切面与程序流的交叉点往往此处需要处理Advice通知或增强在切入点处所要执行的代码。可以理解为切面类中的方法。Target object目标对象指所有被通知的对象。Proxy代理将通知应用到目标对象后被动态创建的对象。Weaving织入将切面代码插到目标对象上从而生成代理对象的过程。
别担心我们之后会通过代码来慢慢理解。
AOP的实现
AOP的实现主要分为静态代理和动态代理在本教程中静态代理我们用AspectJ而动态代理用Spring AOP。
静态代理在编译期就确定了代理类而动态代理需要靠反射机制动态生成代理类。
Spring AOP动态代理有两种实现方式一种是JDK动态代理这种方式需要接口另一种是CGLib动态代理这种方式不依赖接口。
有了以上的知识我们开始写代码首先新创建一个Maven项目top.cairbin.test2如果你不会请回去看之前的章节。
然后在pom.xml的dependencies/dependencies之间添加依赖包
!-- https://mvnrepository.com/artifact/org.springframework/spring-context --
dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.3.16/version
/dependency
!-- https://mvnrepository.com/artifact/cglib/cglib --
dependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion3.3.0/version
/dependency
!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --
dependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactIdversion1.9.4/version
/dependency我们去实现一个IUser接口要求接口内有两个方法void addUser()和void deleteUser()
package top.cairbin.test2;public interface IUser {void addUser();void deleteUser();
}接下来定义一个实现该接口的User类
package top.cairbin.test2;public class User implements IUser{Overridepublic void addUser() {System.out.println(进行增加用户操作);}Overridepublic void deleteUser() {System.out.println(进行删除用户操作!);}
}我们定义一个切面类该类中的两个方法void check()和void log()分别模拟权限检查和日志记录功能。
切面类如下所示
package top.cairbin.test2;public class MyAspect {public void check() {System.out.println(正在模拟权限认证);}public void log() {System.out.println(正在模拟日志记录);}
}JDK动态代理
接下来创建代理类JdkProxy这个类实现了JDK动态代理的InvocationHandler接口并实现代理方法。
package top.cairbin.test2;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class JdkProxy implements InvocationHandler{private final IUser user;public JdkProxy(IUser user) {this.user user;}public static Object createProxy(IUser user) {ClassLoader classLoader JdkProxy.class.getClassLoader();// 被代理对象实现的所有接口Class[] clazz user.getClass().getInterfaces();// 使用代理类进行增强返回的是代理后的对象return Proxy.newProxyInstance(classLoader,clazz,new JdkProxy(user));}Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 声明切面MyAspect myAspect new MyAspect();// 前增强myAspect.check();// 在目标类上调用方法并传入参数Object obj method.invoke(user, args);// 后增强myAspect.log();return obj;}}然后尝试在App.java的main方法中使用它们
package top.cairbin.test2;public class App
{public static void main( String[] args ){// 创建目标对象IUser user new User();// 创建代理并从代理中获取增强后的目标对象IUser user2 (IUser)JdkProxy.createProxy(user);// 执行方法user2.addUser();user2.deleteUser();}
}输出结果如下图所示
CGLib动态代理
我们不妨尝试使用CGLib来玩一下
创建一个新的类名称为CglibProxy并实现接口MethodInterceptor以及相应的方法
package top.cairbin.test2;import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;public class CglibProxy implements MethodInterceptor {public static Object createProxy(Object target){Enhancer enhancer new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(new CglibProxy()); return enhancer.create();}Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 声明切面MyAspect myAspect new MyAspect();// 前增强myAspect.check();//获取增强后的目标对象Object target proxy.invokeSuper(obj, args);// 后增强myAspect.log();return target;}
}尝试调用下
package top.cairbin.test2;public class App
{public static void main( String[] args ){IUser user (IUser)CglibProxy.createProxy (new User());user.addUser();user.deleteUser();}
}不出所料果然成功了
我们仔细观察CGLib的这几段代码在CglibProxy类中我们并没有用到IUser这个接口而是返回Object然后外面也就是调用者那里强制转换为IUser类型
不妨再“懒”一些我们借助Spring的依赖注入从Spring的容器中直接返回增强后的实现了IUser接口的对象试一试。
首先在resources/AppCtx.xml中编写Bean的配置这里有坑如果行不通回前面的文章看看
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd!-- 目标类 --bean iduser classtop.cairbin.test2.User /!-- 切面类 --bean idmyAspect classtop.cairbin.test2.MyAspect /!-- 使用Spring代理工厂定义一个名称为userProxy的代理对象 --bean iduserProxy classorg.springframework.aop.framework.ProxyFactoryBean!-- 指定代理实现的接口--property nameproxyInterfaces valuetop.cairbin.test2.IUser /!-- 指定目标对象 --property nametarget refuser /!-- 指定切面,织入环绕通知 --property nameinterceptorNames valuemyAspect /!-- 指定代理方式true使用cglibfalse(默认)使用jdk动态代理 --property nameproxyTargetClass valuetrue //bean
/beans修改下MyAspect类并实现接口MethodInterceptor
package top.cairbin.test2;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;public class MyAspect implements MethodInterceptor {Overridepublic Object invoke(MethodInvocation mi) throws Throwable {this.check();// 执行目标方法Object obj mi.proceed();this.log();return obj;}public void check() {System.out.println(正在模拟权限认证);}public void log() {System.out.println(正在模拟日志记录);}
}App类中的main方法调用如下
package top.cairbin.test2;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class App
{public static void main( String[] args ){ApplicationContext app new ClassPathXmlApplicationContext(AppCtx.xml);IUser user (IUser)app.getBean(userProxy);user.addUser();user.deleteUser();}
}点击运行得到结果
AspectJ静态代理
使用AspectJ静态代理我们重新设计下MyAspect切面类
package top.cairbin.test2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/*** 切面类在此类中编写通知*/
Aspect
Component
public class MyAspect {// 定义切入点表达式Pointcut(execution(* top.cairbin.test2.*.*(..)))// 使用一个返回值为void、方法体为空的方法来命名切入点private void myPointCut(){}// 前置通知Before(myPointCut())public void myBefore(JoinPoint joinPoint) {System.out.print(前置通知 模拟执行权限检查...,);System.out.print(目标类是joinPoint.getTarget() );System.out.println(,被织入增强处理的目标方法为joinPoint.getSignature().getName());}// 后置通知AfterReturning(valuemyPointCut())public void myAfterReturning(JoinPoint joinPoint) {System.out.print(后置通知模拟记录日志..., );System.out.println(被织入增强处理的目标方法为 joinPoint.getSignature().getName());}// 环绕通知 Around(myPointCut())public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {// 开始System.out.println(环绕开始执行目标方法之前模拟开启事务...);// 执行当前目标方法Object obj proceedingJoinPoint.proceed();// 结束System.out.println(环绕结束执行目标方法之后模拟关闭事务...);return obj;}// 异常通知AfterThrowing(valuemyPointCut(),throwinge)public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {System.out.println(异常通知 出错了 e.getMessage());}// 最终通知After(myPointCut())public void myAfter() {System.out.println(最终通知模拟方法结束后的释放资源...);}
}对于切入点注解Pointcut(execution(* top.cairbin.test2.*.*(..)))表示对top.cairbin.test2这个包下的所有类的所有方法生效。
我们再来看看Spring中的Advice的几种类型
org.springframework.aop.MethodBeforeAdvice前置通知目标方法执行前实施可用于权限管理。org.springframework.aop.AfterReturningAdvice后置通知在目标方法执行后实施用于关闭文件流、上传文件、删除临时文件等。org.aopalliance.intercept.MethodInterceptor环绕通知在目标方法实施前后一般用于日志或事务管理。org.springframework.aop.ThrowsAdvice异常抛出通知在抛出异常后实施。org.springframework.aop.IntroductionInterceptor引介通知在目标类中添加新方法和属性可以应用于修改老版本程序。
我们还要实现自动扫描和依赖注入看看我们的User类
package top.cairbin.test2;
import org.springframework.stereotype.Component;Component
public class User implements IUser{Overridepublic void addUser() {System.out.println(进行增加用户操作);}Overridepublic void deleteUser() {System.out.println(进行删除用户操作!);}
}自然也少不了AppCtx.xml的配置
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:aophttp://www.springframework.org/schema/aopxmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd!-- 指定需要扫描的包使注解生效 --context:component-scan base-packagetop.cairbin.test2 /!-- 启动基于注解的声明式AspectJ支持 --aop:aspectj-autoproxy /
/beans在main方法中测试下为了清楚我这里仅调用了addUser一个方法
package top.cairbin.test2;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class App
{public static void main( String[] args ){ApplicationContext app new ClassPathXmlApplicationContext(AppCtx.xml);IUser user (IUser)app.getBean(user);user.addUser();}
}我们发现当环绕通知与前置通知和后置通知同时使用的时候优先级如下
环绕通知开始前置通知方法执行后置通知环绕通知结束
想必到了这里你对AspectJ的使用有了一定的了解但是对于相应的注解还是不太清楚请仔细阅读下方图片中的表格结合一开始的术语体会下