AOP(Aspect Oriented Programming):是面向切面编程的技术。AOP基于IOC基础,是对OOP的有益补充。
AOP术语名词
Joinpoint:连接点是指所有可能织入通知的方法理论上大部分方法都是连接点
Pointcut:切入点,已经被增强的方法
Advice:通知,增强的方法
Aspect:切面,通知所在的类就叫切面。
Weaving:织入,将通知应用到目标对象来创建新的代理对象的过程。
通知类型及执行顺序
通知的类型: 1.<aop:before > (前置通知) 2.<aop:after-returning > 返回通知 3.<aop:after-throwing > 异常通知 4<aop:after >后置通知5.<aop:around > 环绕通知
通知的执行顺序:
try{
调用前置通知
环绕前置处理
调用目标对象方法
环绕后置处理
调用后置通知
}catch(Exception e){
调用异常通知
}finally{
调用最终通知
}
注意:1.如果目标方法出现异常,将不会执行后置通知 (后置通知是在运行目标方法执行成功后调用的,如果目标方法执行失败(出现异常,后置通知将被调用 )2.最终通知无论是否发生异常都会执行
切入点表达式的写法
关键字:execution(表达式)
表达式格式:
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
标准的表达式写法:
public void com.msg.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.msg.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.msg.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* ....AccountServiceImpl.saveAccount())
包名可以使用…表示当前包及其子包
* …AccountServiceImpl.saveAccount()
类名和方法名都可以使用来实现通配
* ….()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用…表示有无参数均可,有参数可以是任意类型(推荐使用)
全通配写法:
* ….(…)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.msg.service.impl..(…)
Spring中基于XML的AOP配置步骤
-
把切面交给spring来管理
-
使用<aop:config >标签表明开始AOP的配置(以下内容都写在<aop:config >–</aop:config>)
-
使用< aop:pointcut > 指定:切入点
id属性:是给切面提供一个唯一标识
expression属性:用于指定切入点可使用切入点表达式。 -
在<aop:aspect >引入切面
ref属性:引入切面 -
编写通知类型
例如<aop:before >:表示配置前置通知
method属性:用于指定切面中的哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强或者使用pointcut-ref属性引入已经写好<aop:pointcut >
pointcut-ref属性:引入已经写好<aop:pointcut > 也可以使用pointcut属性直接写切入点表达式
<!--1.配置切面-->
<bean id="aj" class="cn.msg.aj.Aj"/>
<!--2.开始AOP配置-->
<aop:config>
<!--3.配置切面表达式-->
<aop:pointcut id="test01" expression="execution(* cn.msg.dao.imp.*.*(..))"/>
<!--4.引入切面-->
<aop:aspect ref="aj">
<!--5.配置通知类型-->
<!--异常通知-->
<aop:after-throwing method="exception" pointcut-ref="test01"/>
<!--环绕通知-->
<aop:around method="around" pointcut-ref="test01"/>
<!--前置通知-->
<aop:before method="befor" pointcut-ref="test01"/>
<!--返回通知-->
<aop:after-returning method="after" pointcut-ref="test01"/>
<!--后置通知-->
<aop:after method="after" pointcut-ref="test01"/>
</aop:aspect>
</aop:config>
Spring中基于注解的AOP配置步骤
1.在xml文件中配置spring创建容器时要扫描的包
<context:component-scan base-package="com.msg"></context:component-scan>
- 配置spring开启注解AOP的支持
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
3.类级注解
@Component(“logger”)注解:将当前类放入容器
@Aspect注解:表示当前类是一个切面类
4.方法级注解
@Pointcut(“execution(* com.msg.service.impl..(…))”)注解:定义切入点
@Before(“pt1()”)注解:配置当前方法为前置通知
@AfterReturning(“pt1()”)注解:配置当前方法为返回通知
@AfterThrowing(“pt1()”)注解:配置当前方法为异常通知
@After(“pt1()”)注解:配置当前方法为后置通知
@Around(“pt1()”)注解:配置当前方法为环绕通知
动态代理
proxy-target-class属性:值决定是基于接口的代理还是基于类的代理被创建。首先说明下proxy-target-class="true"和proxy-target-class="false"的区别,为true则是基于类的代理将起作用(需要cglib库),为false或者省略这个属性,则标准的JDK 基于接口的代理将起作用。
默认动态代理是jdk动态代理,而jdk动态代理不支持类注入也就是依赖注入的对象不能是类,只能是接口
在以下标签中可设置动态代理:
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
<aop:config proxy-target-class="true">
<cache:annotation-driven proxy-target-class="true"/>
切面中的环绕通知(其他通知可按正常方法书写)
环绕通知
<!--环绕通知需要的jar-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
public Object around(ProceedingJoinPoint pjp){
Object rt = null;
try {
Object[] args = pjp.getArgs();//得到执行方法所需的参数
System.out.println("前置");
rt = pjp.proceed(args);//执行方法
System.out.println("返回");
return rt;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常");
}finally {
System.out.println("后置");
}
return null;
}