【第一章】AOP简介和入门案例
1 什么是AOP?
面向切面编程,切面指定就是动态代理的方法,作用是在不改变业务层方法源代码的基础上对方法进行增强,底层使用的是动态代理技术,面向切面编程也可以理解成面向动态代理编程。
2 AOP专业术语
- Target(目标对象):被代理的对象就是目标对象
- Proxy(代理对象):被增强后的对象就是代理对象
- Joinpoint(连接点):就是目标对象中所有被拦截到的方法
- Pointcut(切入点):就是目标对象中被增强的方法
- Advice(通知):执行目标方法之前或者之后调用的方法就是通知
- Aspect(切面):通知方法和切入点方法结合所在的位置叫做切面
- Weaving(织入):通知方法和切入点方法结合的过程,织入之后的结果就是切面
连接点是所有被拦截到的方法,切入点是所有被增强的方法,连接点不一定是切入点,但是切入点一定是连接点。在执行目标对象方法之前或者之后要做的事叫做通知,通知中有增强的业务。将切入点和通知组织到一起叫织入,织入形成的结果就是切面。
3 入门案例【重点】
实现步骤
【第一步】导入相关依赖:spring-context、aspectjweaver
【第二步】定义通知类和目标对象(已完成)
【第三步】xml文件配置AOP
【第一步】导入相关依赖:spring-context、aspectjweaver
<!--spring核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!--切入点表达式依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
【第二步】定义通知类和目标对象(已完成)
- 通知类
//通知类,告诉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("最终通知:释放资源");
}
}
- 目标类
public interface StudentService {
//查询全部
public abstract List<Student> findAll() throws IOException;
//根据id查询
public abstract Student findById(Integer id) throws IOException;
//新增数据
public abstract void insert(Student stu) throws IOException;
//修改数据
public abstract void update(Student stu) throws IOException;
//删除数据
public abstract void delete(Integer id) throws IOException;
}
public class StudentServiceImpl implements StudentService {
@Override
public List<Student> findAll() throws IOException {
System.out.println("查询所有学生信息findAll...");
return null;
}
@Override
public Student findById(Integer id) throws IOException {
System.out.println("根据id查询学生信息findById...");
return null;
}
@Override
public void insert(Student stu) throws IOException {
//执行添加操作
System.out.println("添加学生信息insert...");
}
@Override
public void update(Student stu) throws IOException {
System.out.println("修改学生信息update...");
}
@Override
public void delete(Integer id) throws IOException {
System.out.println("根据id删除学生信息delete...");
int i=100/0;
}
}
【第三步】xml文件配置AOP
<?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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--1 配置目标对象:StudentServiceImpl-->
<bean id="studentService" class="com.itheima.service.impl.StudentServiceImpl"/>
<!--2 配置通知对象:Advice-->
<bean id="advice" class="com.itheima.aop.Advice"/>
<!--3 AOP配置切面,通知+切入点-->
<aop:config>
<!--3.1 配置切入点,此处配置在所有切面中都可以用-->
<aop:pointcut id="pt" expression="execution( * com.itheima.service.impl.StudentServiceImpl.*(..))"/>
<!--3.2 配置通知+切入点-->
<aop:aspect ref="advice">
<!--配置切入点,此处配置的只能在当前的切面中用-->
<!--<aop:pointcut id="pt" expression="execution( * com.itheima.service.impl.StudentServiceImpl.*(..))"/>-->
<!--配置前置通知,
method="before"表示执行通知类的before方法
pointcut="execution()" 切入点表达式,用于找到要代理的方法
pointcut-ref="pt" 表示引用id="pt"的切入点表达式
-->
<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:aspect>
</aop:config>
</beans>
【第二章】AOP的xml配置
1 切入点表达式的写法【重点】
execution( * com.itheima.service.impl.StudentServiceImpl.findAll(..) //较少
execution( * com.itheima.service.impl.StudentServiceImpl.*(..) //较少
execution( * com.itheima.service.StudentService.*(..) //StudentService中的所有方法会被代理,比较常用
execution( * com..StudentService.*(..) //StudentService中的所有方法会被代理,比较常用
execution( * com..service.*.*(..) //com包下的所有子包下的service包中的所有类的所有方法,比较常用
常见问题:如果使用AOP给接口/类配置了增强,那么只能用接口或父类接收代理对象。
'Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named ''studentService' 'is expected to be of type ''com.itheima.service.impl.StudentServiceImpl'' but was actually of type com.sun.proxy.$Proxy17 <-----以上是核心信息'
'at org.springframework.beans.factory.support.DefaultListableBeanFactory.checkBeanNotOfRequiredType(DefaultListableBeanFactory.java:1672)
' at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1650)
' at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1213)
' at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
' at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593)
... 27 more
2 通知类型【重点】
前置通知:在目标方法执行之前执行
后置通知/后置返回通知:在目标方法调用之后执行
异常通知/后置异常通知:在调用目标方法出现异常时执行
最终通知:调用目标方法不管是否出现异常,都会执行,相当于finally中的代码
----------以上四种通知无法阻止目标方法调用,目标方法是有Spring来控制------------------
----------环绕通知可以代替以上四种通知----------------
环绕通知:是Spring给我们提供的一种手动调用目标对象方法或者其他通知的通知方式
通知使用说明:要么单独使用环绕通知,要么组合其他四种通知一起使用,两种方案二选一。
3 在通知中获取目标方法的参数、返回值、异常信息(了解)
前置通知中获取目标方法的参数
public void before(JoinPoint jp){
//获取请求参数
//doBefore(jp.getArgs());
Object[] args = jp.getArgs();
System.out.println("args = " + Arrays.toString(args));
//前置通知:开启事务
System.out.println("Advice前置通知:开启事务");
}
后置通知中获取目标方法的结果/返回值
<aop:after-returning method="afterReturn" pointcut-ref="pt" returning="result"/>
public void afterReturn(Object result){ //result是结果
//后置通知:提交事务
System.out.println("Advice后置通知:提交事务 "+result);
}
说明:后置通知方法的形参变量名要和配置中的returning="result"属性值一样
异常通知中获取异常信息
<aop:after-throwing method="afterThrowable" pointcut-ref="pt" throwing="err"/>
public void afterThrowable(Throwable err){
//异常通知:回滚事务
System.out.println("Advice异常通知:回滚事务 " +err.getMessage());
}
说明:异常通知方法的形参变量名要和配置中的throwing="err"属性值一样
环绕通知可以获取以上所有
try {
//前置增强
//doBefore(pjp.getArgs());
//手动调用目标方法
//获取切入点方法的参数
Object[] args = pjp.getArgs(); //参数
//执行切入点方法,也就是执行目标方法
value= pjp.proceed(args); //返回值就是目标方法的返回值,结果
//后置增强
//afterReturn();
} catch (Throwable throwable) { //异常
throwable.printStackTrace();
//异常增强
//afterThrowable();
} finally {}
【第三章】AOP的注解配置【重点】
【第一步】开启spring的AOP注解扫描
- xml文件配置
<!--开启spring的AOP注解扫描-->
<aop:aspectj-autoproxy/>
- 纯注解配置
@Configuration
@ComponentScan("com.itheima")// <context:component-scan base-package="com.itheima"/>
@EnableAspectJAutoProxy // <aop:aspectj-autoproxy/>
public class SpringConfiguration {
}
【第二步】在通知类上使用@aspect表示这个类是一个切面类
@Component
@Aspect //该类即是一个通知类也是一个切面类
public class Advice{
...
}
【第三步】定义切入点表达式方法
//定义切入点表达式的方法,方法体不需要写任何内容,方法名就是切入点的id
//<aop:pointcut id="pt" expression="execution( * com.itheima.service.StudentService.*(..))"/>
@Pointcut("execution( * com.itheima.service.StudentService.*(..))")
public void pt(){}
【第四步】在通知方法上加对应的注解
@Before("pt()")
public void before(){
//前置通知:开启事务
System.out.println("Advice前置通知:开启事务");
}
@AfterReturning("pt()")
public void afterReturn(){ //result是结果
//后置通知:提交事务
System.out.println("Advice后置通知:提交事务");
}
@AfterThrowing("pt()")
public void afterThrowable(){
//异常通知:回滚事务
System.out.println("Advice异常通知:回滚事务 ");
}
@After("pt()")
public void after(){
//最终通知:释放资源
System.out.println("Advice最终通知:释放资源");
}
----------------------------------------------
@Around("pt()")
public Object around(ProceedingJoinPoint pjp){
//System.out.println("环绕通知执行了...");
Object value=null;
try {
//前置增强
before();
//手动调用目标方法
//获取切入点方法的参数
Object[] args = pjp.getArgs();
//执行切入点方法,也就是执行目标方法
value= pjp.proceed(args); //返回值就是目标方法的返回值
//后置增强
afterReturn();
} catch (Throwable throwable) {
throwable.printStackTrace();
//异常增强
afterThrowable();
} finally {
//最终通知
after();
}
return value;
}
注意:使用注解配置AOP,后置通知和异常通知会在最终通知之后调用,在spring-context的5.1.5版本中是这样的,在更高的版本中可能得到了解决,大家自己验证。但是我们可以使用环绕通知解决这个问题,推荐使用环绕通知。
【第四章】综合案例-业务层接口性能监控
【第五章】AOP底层原理
静态代理/装饰模式、jdk中的动态代理、cglib动态代理、织入时机。
Spring的AOP底层默认是采用jdk的动态代理,也可以通过配置使用cglib动态代理,配置如下:
- 纯xml配置
<!--proxy-target-class="true" true表示使用cglib动态代理,默认值是false表示jdk动态代理-->
<aop:config proxy-target-class="true">
...
</aop:config>
- 半xml半注解
<!--开启spring的AOP注解扫描-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
- 纯注解配置
@Configuration
@ComponentScan("com.itheima")// <context:component-scan base-package="com.itheima"/>
@EnableAspectJAutoProxy(proxyTargetClass = true) // <aop:aspectj-autoproxy/>
public class SpringConfiguration {
}
今日总结
1 AOP : 面向切面编程,不改变业务层方法源代码的情况下对业务层方法进行增强,底层默认使用jdk的动态代理
AOP前提:导入依赖坐标:spring-context、aspectjweaver
xml配置:
1 配置我们的业务层实现类
<bean id="studentService" class=""></bean>
2 配置我们的通知类
<bean id="advice" class=""></bean>
3 AOP配置(重要)
<!--proxy-target-class="true" 表示使用cglib动态代理-->
<aop:config proxy-target-class="true">
<!--配置切面-->
<aop:aspect ref="advice">
<!--配置切入点表达式-->
<aop:pointcut id="pt" expression="execution( * com.itheima.service.StudentService.*(..))"/>
<!--配置不同的通知类型-->
<!--配置前置通知,
method="before"表示执行通知类的before方法
pointcut="execution( * com.itheima.service.impl.StudentServiceImpl.findById(..))" 切入点表达式,用于找到要代理的方法
pointcut-ref="pt" 表示引用id="pt"的切入点表达式
-->
<aop:before method="before" pointcut-ref="pt"/>
<!--后置通知,也叫后置返回通知-->
<aop:after-returning method="afterReturn" pointcut-ref="pt" returning="result"/>
<!--异常通知,也叫后置异常通知-->
<aop:after-throwing method="afterThrowable" pointcut-ref="pt" throwing="err"/>
<!--最终通知-->
<aop:after method="after" pointcut-ref="pt"/>
<!--环绕通知-->
<!--<aop:around method="around" pointcut-ref="pt"/>-->
</aop:aspect>
</aop:config>
注解配置:
第一步:开启Spring的AOP注解支持
在xml文件中
<!--proxy-target-class="true" 表示使用cglib动态代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
在配置类中:
@EnableAspectJAutoProxy(proxyTargetClass = true)
第二步:在通知类上使用@Aspect表示该类是一个切面类
@Component
@Aspect //该类即是一个通知类也是一个切面类
public class Advice {}
第三步:定义切入点表达式方法,在方法上使用@Pointcut注解指定切入点表达式
@Pointcut("execution( * com.itheima.service.StudentService.*(..))")
public void pt(){}
第四步:在通知方法上加对应的注解表示不同的通知类型
@Before、@AfterReturning、@AfterThrowable、@After、@Around
注意:注解的方式推荐使用环绕通知。