1 什么是AOP?
- Aspect Oriented Programming的缩写,面向切面编程,切面指定就是动态代理的方法,作用是在不改变业务层方法源代码的基础上对方法进行增强,底层使用的是动态代理技术,面向切面编程也可以理解成面向动态代理编程。
2 AOP相关概念
- Target(目标对象):被代理的对象就是目标对象
- Proxy(代理对象):被增强后的对象就是代理对象
- Joinpoint(连接点):就是目标对象中所有被拦截到的方法
- Pointcut(切入点):就是目标对象中被增强的方法
- Advice(通知):执行目标方法之前或者之后调用的方法就是通知
- Aspect(切面):通知方法和切入点方法结合所在的位置叫做切面
- Weaving(织入):通知方法和切入点方法结合的过程,织入之后的结果就是切面
总结一下:
连接点是所有被拦截到的方法,切入点是所有被增强的方法,连接点不一定是切入点,但是切入点一定是连接点。在执行目标对象方法之前或者之后要做的事叫做通知,通知中有增强的业务。将切入点和通知组织到一起叫织入,织入形成的结果就是切面。
3 AOP配置实现步骤
<1>【第一步】导入相关依赖:spring-context、aspectjweaver
<!--spring核心依赖-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.9.RELEASE</version>
</dependency>
<!--切入点表达式依赖,作用:通过表达式找到哪些方法需要增强,也就是找到切入点-->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version>
</dependency>
<2>【第二步】定义通知类和目标对象
- AOP目标接口:
public interface StudentService {//查询全部public abstract List<Student> findAll() throws IOException;public void transfer(Integer outId, Integer inId, double money);
}
- AOP目标实现类:
@Service("studentService")
public class StudentServiceImpl implements StudentService {@Overridepublic List<Student> findAll() throws IOException {System.out.println("查询所有学生信息findAll...");return null;}@Overridepublic void transfer(Integer outId,Integer inId,double money){//1 张三的账户-1000元System.out.println("调用dao:张三("+outId+")的账户"+(-money)+"元");//2 李四的账户+1000元System.out.println("调用dao:李四("+inId+")的账户"+money+"元");}//接口中没有该方法,不会被拦截public void show(){System.out.println("----------------");}
}
注:代理的为接口对象,接口中没有的方法,实现类自己的方法不会被增强
- 通知类:
import org.aspectj.lang.ProceedingJoinPoint;//通知类,告诉spring在增强的前后需要做什么事
public class Advice {public void before(){//前置通知:开启事务System.out.println("前置通知:开启事务");}public void afterReturn(){//后置通知:提交事务System.out.println("后置通知:提交事务");}public void afterThrowable(){//异常通知:回滚事务System.out.println("异常通知:回滚事务");}public void after(){//最终通知:释放资源System.out.println("最终通知:释放资源");}// 环绕通知:是Spring给我们提供的一种手动调用目标对象方法或者其他通知方法的方式// spring在调用环绕通知方法时会传递一个封装了目标方法的对象,叫做ProceedingJoinPointpublic Object around(ProceedingJoinPoint pjp){Object result =null;try {//前置通知before();//执行目标方法,相当于动态代理中的 result=method.invoke(...)result = pjp.proceed();//后置通知afterReturn();} catch (Throwable throwable) {//异常通知afterThrowable();throwable.printStackTrace();} finally {//最终通知after();}return result;}
}
<3>xml文件配置AOP
1 配置目标对象,添加到spring容器中2 配置通知对象,添加到spring容器中3 配置切入点方法和通知方法织入过程,也就配置切面
- 纯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:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><!--1.配置service--><bean id="studentService" class="com.itheima.service.impl.StudentServiceImpl"/><!--2.配置通知对象--><bean id="myAdvice" class="com.itheima.aop.Advice"/><!--3.配置AOP--><aop:config><!--3.1配置AOP切入点表达式[可放在任意位置]--><!--*空格代表void 方法名.*:.StudentServiceImpl.*:.方法名(..)方法参数, ..代表参数任意--><aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.*.*(..))"/><!--<aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.StudentServiceImpl.*(..))"/>--><!--3.2配置切面--><aop:aspect ref="myAdvice"><!--前置通知--><aop:before method="before" pointcut-ref="pt"/><!--后置通知--><aop:after-returning method="afterReturn" pointcut-ref="pt"/><!--异常通知--><aop:after-throwing method="afterThrowable" pointcut-ref="pt"/><!--最终通知--><aop:after method="after" pointcut-ref="pt"/><!--使用环绕通知--><!--<aop:around method="around" pointcut-ref="pt"/>--></aop:aspect></aop:config>
</beans>
- 注解配置:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;//通知类,告诉spring在增强的前后需要做什么事
@Component("advice")
@Aspect//告知是一个切面类,扫描时会扫描它的注解//代替:<aop:aspect ref="advice">
public class Advice {@Pointcut("execution(* com.itheima.service.impl.*.*(..))")//id为方法名[首字母小写]public void pt() {}//==注意:使用注解配置AOP,后置通知和异常通知会在最终通知之后调用,
// 在spring-context的5.1.9版本中是这样的,在更高的版本中可能得到了解决,
// (5.2.6及以上版本解决了)。
// 但是我们可以使用环绕通知解决这个问题,推荐使用环绕通知。==**/* @Before("pt()")public void before(JoinPoint joinPoint) {Object[] args = joinPoint.getArgs();//前置通知:开启事务System.out.println("前置通知:开启事务"+args[0]);}*/
//两种得到传递参数的方法 如果目标方法没有传参,则不执行@Before("execution(* com.itheima.service.impl.*.*(..))&&args(x)")public void before(int x) {//前置通知:开启事务System.out.println("前置通知:开启事务" + x);}@AfterReturning("pt()")public void afterReturn() {//后置通知:提交事务System.out.println("后置通知:提交事务");}@AfterThrowing("pt()")public void afterThrowable() {//异常通知:回滚事务System.out.println("异常通知:回滚事务");}@After("pt()")public void after() {//最终通知:释放资源System.out.println("最终通知:释放资源");}// 环绕通知:是Spring给我们提供的一种手动调用目标对象方法或者其他通知方法的方式// spring在调用环绕通知方法时会传递一个封装了目标方法的对象,叫做ProceedingJoinPoint//@Around("pt()")public Object around(ProceedingJoinPoint pjp) {Object result = null;try {//前置通知//Object[] args = pjp.getArgs();//before();//执行目标方法,相当于动态代理中的 result=method.invoke(...)result = pjp.proceed();//后置通知afterReturn();} catch (Throwable throwable) {//异常通知afterThrowable();throwable.printStackTrace();} finally {//最终通知after();}return result;}
}
注:
使用注解配置AOP,后置通知和异常通知会在最终通知之后调用,
在spring-context的5.1.9版本中是这样的,在更高的版本中可能得到了解决,
(5.2.6及以上版本解决了)。
但是我们可以使用环绕通知解决这个问题,推荐使用环绕通知
- 核心配置类代替XML
import org.springframework.context.annotation.*;@Configuration//表示代表替换applicationContext.xml的标识[可以不写]
@ComponentScan("com.itheima")//开启Spring注解扫描
@EnableAspectJAutoProxy//开启Spring的AOP注解支持
public class SpringConfig {
}
4.底层动态代理类似原理[studentService动态代理工厂]
package com.itheima.proxy;
import com.itheima.aop.Advice;
import com.itheima.service.StudentService;
import com.itheima.service.impl.StudentServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class StudentServiceProxyFactory {public static StudentService createStudentServiceProxy() {Advice advice = new Advice();//1.创建真实对象StudentService studentService = new StudentServiceImpl();//可采用set注入//2.创建代理对象/**ClassLoader loader,创建代理对象的class对象Class<?>[] interfaces,告诉代理对象要和目标对象实现相同的接口,就具有相同的功能。InvocationHandler h,处理增强的逻辑*/ClassLoader classLoader = studentService.getClass().getClassLoader();Class<?>[] interfaces = studentService.getClass().getInterfaces();//获取所有的直接实现的接口StudentService service = (StudentService) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {/*** @param proxy 代理对象* @param method 调用代理对象的方法,findAll、findById、transfer、update。。。* @param args 调用代理对象方法传递进来的参数们* @return 此处的返回值将返回给调用处* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = null;if (method.getName().equals("transfer") || method.getName().equals("delete")) {try {//1.开启事务advice.before();//2.执行操作,调用目标方法result = method.invoke(studentService, args);//3.提交事务advice.afterReturn();} catch (Exception e) {e.printStackTrace();//4.如果有异常则回滚事务advice.afterThrowable();} finally {//5.释放资源advice.after();}} else {//执行操作,调用目标方法result = method.invoke(studentService, args);}return result;}});return service;}
}
注:只可对单一实现类对象进行增强