1、以初学者的视角记录笔记 2、课程链接:[链接] 3、资料链接:[笔记对应课程的配套资料],提取码:saq4 4、实操很重要!!! 5、本文主讲面向切面编程(AOP) 6、前文链接:[Spring入门笔记1]
从前一篇文章中我们有了IoC可以帮我们管理类实例。
又有新的问题出现了,
如果多个上层实例都调用一个下层实例(纵向的继承机制),
很容易导致牵一发而动全身,
换句话说就是耦合度太高了,
所以需要一种解耦的手段,
即 面向切面编程 AOP(Aspect Oriented Programming)这种横向的抽取机制,
不使用继承,而是直接插入函数前或后。
aop底层将采用代理机制进行实现,有两种实现方法。 1、使用 jdk的Proxy 拦截来实现:有接口的实现类(默认) 2、使用 cglib字节码 子类增强来实现:有接口的实现类、无接口的实现类(更强大)
通过下面的代码帮助理解AOP的原理(不使用Spring和AspectJ)。
接口:
public interface IUserService { public void addUser(); public void updateUser(); public void deleteUser(); public int deleteUser(int id); }实现类target:
public class UserServiceImpl implements IUserService { @Override public void addUser() { System.out.println("添加用户。。。。"); } @Override public void updateUser() { System.out.println("更新用户。。。。"); } @Override public void deleteUser() { System.out.println("删除用户。。。。"); } @Override public int deleteUser(int id) { System.out.println("通过id删除用户"); return 1; } }切面类:
public class MyAspect { public void before(){ System.out.println("开启事务..."); } public void after(){ System.out.println("提交事务..."); } }调用的运行代码:
public class Lesson04 { @Test public void test1() throws Exception { IUserService userService = MyBeanFactory.createUserService(); userService.deleteUser(10); userService.addUser(); userService.updateUser(); } }
工厂类:
public class MyBeanFactory { public static IUserService createUserService(){ // 1.创建目标对象target final IUserService userService = new UserServiceImpl(); // 2.声明切面类对象 final MyAspect2 aspect = new MyAspect2(); // 3.创建JDK代理,拦截方法,下面是JDK代理函数的参数 /*newProxyInstance( ClassLoader loader, 类加载器,写当前类 Class<?>[] interfaces, 目标类接口,接口的方法会被拦截 InvocationHandler h) 处理 */ IUserService seriviceProxy = (IUserService) Proxy.newProxyInstance( MyBeanFactory.class.getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 开启事务 aspect.before(); // method返回值是 目标类方法的返回值 Object retObj = method.invoke(userService,args); // 提交事务 aspect.after(); return retObj; } } ); return seriviceProxy; } }
没有接口,只有实现类。 采用字节码增强框架 cglib,在运行时创建目标类的子类,从而对目标类进行增强。
工厂类:
public class MyBeanFactory { public static StudentService createStudentService() { //1.创建目标对象target final StudentService studentService = new StudentService(); //2.声明切面类对象 final MyAspect2 aspect = new MyAspect2(); //3.创建增强对象 Enhancer enhancer = new Enhancer(); //设置父类 enhancer.setSuperclass(studentService.getClass()); //设置回调【拦截】 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { /** * proxy: * om.gyf.service.StudentService$$EnhancerByCGLIB$$fbb8ef26 * proxy代理对象是StudentService的子类 */ aspect.before(); // 放行方法 // Object retObj = method.invoke(studentService,args); Object retObj = methodProxy.invokeSuper(proxy, args); // 解耦,比上一行的方法好 aspect.after(); return retObj; } }); //创建代理对象 StudentService serviceProxy = (StudentService) enhancer.create(); return serviceProxy; } }
第四部分主要介绍怎么调用Spring的AOP。
半自动是使用Spring容器配置代理,全自动是通过Spring容器使用AspectJ。
Spring按照Advice在target方法的joinpoint位置,分为5下面类。
类型解释包前置通知在目标方法执行前实施增强org.springframework.aop.MethodBeforeAdvice后置通知在目标方法执行后实施增强org.springframework.aop.AfterReturningAdvice环绕通知在目标方法执行前后实施增强org.aopalliance.intercept.MethodInterceptor异常抛出通知在方法抛出异常后实施增强org.springframework.aop.ThrowsAdvice引介通知在目标类中添加一些新的方法和属性org.springframework.aop.IntroductionInterceptor
使用Spring的配置文件进行代理 1、之前的文章中已经导入了如下6个jar包 commons-logging-1.x.jarspring-aop-x.x.x.RELEASE.jarspring-beans-x.x.x.RELEASE.jarspring-context-x.x.x.RELEASE.jarspring-core-x.x.x.RELEASE.jarspring-expression-x.x.x.RELEASE.jar 2、现在还需要导入AOP联盟的jar包 >>>下载地址 3、解压aopalliance.zip后把aopalliance文件夹的aopalliance.jar放到之前自己的项目依赖文件夹中下 4、编写代码(不需要工厂类)
接口和实现类不变
切面类:
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class MyAspect implements MethodInterceptor{ @Override public Object invoke(MethodInvocation mi) throws Throwable { //拦截方法 System.out.println("开启事务..."); //放行 Object retObj = mi.proceed(); System.out.println("拦截....."); System.out.println("提交事务..."); return retObj; } }配置文件:
<!-- 配置UserService--> <bean id="userService" class="com.gyf.service.UserServiceImpl"></bean> <!-- 配置切面类对象--> <bean id="myAspect" class="com.gyf.aspect.MyAspect"></bean> <!-- 配置代理对象 默认情况下Spring的AOP生成的代理是JDK的Proxy实现的 --> <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 接口 :如果只是一个接口,就写Value,如果是多个接口就写List--> <property name="interfaces" value="com.gyf.service.IUserService"> </property> <!-- 目标对象 --> <property name="target" ref="userService"/> <!-- 切面类--> <property name="interceptorNames" value="myAspect"></property> <!-- 配置使用cglib生成--> <property name="optimize" value="true"></property> </bean>调用的运行代码:
public class Lesson07 { @Test public void test1() throws Exception { //获取Spring容器中代理对象 ApplicationContext context = new ClassPathXmlApplicationContext("beans07.xml"); IUserService userService = (IUserService) context.getBean("serviceProxy"); userService.deleteUser(); } }
1、下载aspectj.jar后放到项目依赖文件夹中。
如果报错找不到包,把aspectj-1.x.x.jar/files/lib中的aspectjrt.jaraspectjtools.jaraspectjweaver.jar放在依赖文件夹中。
2、编写代码(不需要工厂类)
接口、实现类、调用的运行代码不变
配置文件:第5、10、11行新增了命名空间
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p ="http://www.springframework.org/schema/p" 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"> <!-- 配置UserService--> <bean id="userService" class="com.gyf.service.UserServiceImpl"></bean> <!-- 配置切面类对象--> <bean id="myAspect" class="com.gyf.aspect.MyAspect"></bean> <!-- 全自动AOP配置 1.在bean中配置aop约束 2.配置aop:conifg内容,把切入点和通知结合 proxy-target-class:使用cglib实现代理 expression 表达式:*表示任意 execution(* com.gyf.service.*. * (..)) 返回值 包名 类名 方法名 参数 AOP:用于事务配置&日志记录 --> <aop:config proxy-target-class="true"> <aop:pointcut id="myPointcut" expression="execution(* com.gyf.service.*.*(..))"/> <!-- 通知 关联 切入点--> <aop:advisor advice-ref="myAspect" pointcut-ref="myPointcut"></aop:advisor> </aop:config> </beans>
第四部分主要介绍 AOP联盟接口 的 AspectJ实现 。
AspectJ是一个基于Java语言的AOP框架,
Spring2.0以及上版本自带AspectJ,
建议使用AspectJ来更加灵活的开发AOP。(第四部分的全自动就是使用的AspectJ)
aop联盟定义通知类型,aop联盟的jar都是接口,必须要有实现类。
aspectj 通知类型,只定义类型名称,以及方法格式。
类型名称解释应用配置文件中的对应before前置通知在方法执行前执行,如果通知抛出异常,阻止方法运行各种校验aop:beforearound环绕通知方法执行前后分别执行,可以阻止方法的执行。必须手动执行目标方法可以做任何事情aop:aroundafterReturning后置通知方法正常返回后执行,如果方法中抛出异常,通知无法执行。必须在方法执行后才执行,所以可以获得方法的返回值。常规数据处理aop:after-returningafterThrowing抛出异常通知方法抛出异常后执行,如果方法没有抛出异常,无法执行包装异常信息aop:after-throwingafter最终通知方法执行完毕后执行,无论方法中是否出现异常清理现场aop:afterxml配置的执行顺序:(afterThrowing任意时刻都有可能触发) before ->around的前置通知 ->afterReturning ->around的后置通知 ->after
1、execution()【重要】 作用:用于描述joinpoint 语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常)
位置类型示例是否可以省略修饰符public【公共方法】*【任意】是(建议)返回值void【返回没有值】String【返回值字符串】*【任意】否(强制)包com.gyf.crm【固定包】com.gyf.crm.*.service【crm包下面子包任意】 (例如:com.gyf.crm.staff.service)com.gyf.crm…【crm包下面的所有子包(含自己)】com.gyf.crm.*.service…【crm包下面任意子包,固定目录service,service目录任意包】否(建议)类UserServiceImpl【指定类】*Impl【以Impl结尾】User*【以User开头】*【任意】否(建议)方法名addUser【固定方法】add*【以add开头】*Do【以Do结尾】*【任意】否(强制)参数()【无参】(int)【一个整型】(int ,int)【两个】(…)【参数任意】否(强制)throws异常与java中相同是(建议)2、其他表达式【了解】
表达式作用例子within()匹配包或子包中的方法within(com.gyf.aop..*)this()匹配实现接口的目标对象中的方法target(com.gyf.aop.user.UserDAO)args()匹配参数格式符合标准的方法args(int,int)bean(id)匹配指定的bean所有的方法bean('userServiceId')
1、导入 spring-aspects-x.x.x.RELEASE.jar 到依赖文件夹(经测试Spring5.2.8版本不需要) 2、编写代码
接口、实现类、调用的运行代码不变
切面类:
import org.aspectj.lang.ProceedingJoinPoint; public class MyAspect{ public void myBefore(JoinPoint jp){ // 怎么获取方法名(都可以使用这个方法,包括around的ProceedingJoinPoint类) System.out.println("1.前置通知... 连接点方法名:" + jp.getSignature().getName()); } public void myAfterReturning(Object retValue){ System.out.println("3.后置通知..."); // 怎么获取返回值(只有AfterReturning才能获取返回值,且要在xml中配置returning属性) System.out.println("返回值:" + retValue); } // ProceedingJoinPoint是手动放行的连接点;JoinPoint是自动放行的连接点 public Object myAround(ProceedingJoinPoint pjp) throws Throwable { System.out.println("2.环绕通知-开启事务..."); // 放行 Object retObj = pjp.proceed(); System.out.println("4.环绕通知-提交事务..."); return retObj; } public void myAfterThrowing(JoinPoint jp, Throwable e){ // 异常通知最好有方法名 System.out.println("异常通知..." + jp.getSignature().getName() + "===" + e.getMessage() ); } public void myAfter(){ System.out.println("最终通知..."); } }配置文件:(命名空间与四-(二)相同)
<!-- 配置UserService --> <bean id="userService" class="com.gyf.service.UserServiceImpl"></bean> <!-- 配置切面对象 --> <bean id="myAspect" class="com.gyf.aspect.MyAspect"></bean> <!-- 配置 aop --> <aop:config> <!-- aop:指定切面 --> <aop:aspect ref="myAspect3"> <!-- 定义一个切入点 --> <aop:pointcut id="myPointcut" expression="execution(* com.gyf.service.UserServiceImpl.*(..))"/> <!-- 配置前置通知 before --> <aop:before method="myBefore" pointcut-ref="myPointcut" /> <!-- 配置后置通知 after-returning --> <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/> <!-- 配置环绕通知 around --> <aop:around method="myAround" pointcut-ref="myPointcut"/> <!-- 配置异常通知 throwing="e" 值,是 调用方法 形参 的 "Throwable"类型 的 变量名 --> <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/> <!--配置最终通知:不管有没有异常,最终通知都会执行--> <aop:after method="myAfter" pointcut-ref="myPointcut"/> </aop:aspect> </aop:config>
因为比较常用,所以单独拎出来 1、声明使用注解
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p ="http://www.springframework.org/schema/p" 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.gyf"/> <!-- 配置aop注解生效 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- aop配置,切面类方法 --> <aop:config> <aop:aspect ref="myAspect"></aop:aspect> </aop:config> </beans>2、使用的注解类型可以查看Spring入门笔记1的第六部分。 3、切面类中针对AOP的特殊注解 切面类:
@Component @Aspect import org.aspectj.lang.ProceedingJoinPoint; public class MyAspect{ //声明一个公共的切入点 @Pointcut("execution(* com.gyf.service.UserServiceImpl.*(..))") public void myPointcut(){} @Before("myPointcut()") public void myBefore(JoinPoint jp){ System.out.println("1.前置通知... 连接点方法名:" + jp.getSignature().getName()); } @AfterReturning(pointcut = "myPointcut()",returning = "retValue") public void myAfterReturning(Object retValue){ System.out.println("3.后置通知..."); System.out.println("返回值:" + retValue); } @Around("myPointcut()") public Object myAround(ProceedingJoinPoint pjp) throws Throwable { System.out.println("2.环绕通知-开启事务..."); Object retObj = pjp.proceed(); System.out.println("4.环绕通知-提交事务..."); return retObj; } @AfterThrowing(pointcut = "myPointcut()",throwing = "e") public void myAfterThrowing(JoinPoint jp, Throwable e){ System.out.println("异常通知..." + jp.getSignature().getName() + "===" + e.getMessage() ); } @After("myPointcut()") public void myAfter(){ System.out.println("最终通知..."); } } 注解切入位置@Aspect声明切面,修饰切面类,从而获得 通知。@PointCut修饰方法 private void xxx(){} 之后通过“方法名”获得切入点引用@Before前置@AfterReturning后置@Around环绕@AfterThrowing抛出异常@After最终
本文最需要关注的部分在第四五六部分
从Spring代理->AspectJ的XML自动代理->AspectJ的注解自动代理
从原理出发,逐渐简化代码,全文最重要也是最实用的在第六部分——使用注解配置AOP