spring的aop(面向切面编程),他可以在不改变源码的情况下,对已有的方法进行增强操作。
AOP中的常用术语
Joinpoint(连接点):连接点是那些被连接到的点;在spring中,这些连接点指的是方法,因为因为 spring 只支持方法类型的连接点。
Pointcut(切入点):切入点是指要对那些连接点进行拦截的定义。
Advice(通知/增强):通知是指拦截到连接点之后所要做的事情,通知可以分为前置通知、后置通知、异常通知、最终通知和环绕通知。
Introduction(引介):引介是一种特殊的通知,在不修改类代码的前提条件下,引介可以在运行期为类动态地添加一些方法或Filed。
Target(目标对象):代理的目标对象。
Weaving(织入):指定把增强应用到目标对象来创建新的代理对象的过程。
Proxy(代理):一个类被AOP织入增强后,就会产生一个结果代理类。
Aspect(切面):他是切入点和通知(引介)的结合。
通知类型
前置通知 :在目标方法执行之前执行.。
后置通知 :在目标方法正常执行之后执行,他与异常抛出通知互斥。
环绕通知 :因为在环绕通知中可以手动调用切入点方法,所以环绕通知可以在目标方法执行前或执行后执行。
异常抛出通知:在目标方法执行出现异常的时候执行,他与后置通知互斥。
最终通知 :无论目标方法是否出现异常最终通知都会执行.
切入点表达式
在spring中,这样切入点表达式中的方法才会被增强,spring使用execution关键字来定义切入点表达式
表达式的写法:
表达式写法:
访问修饰符 返回值 包名.包名…包名.类名.方法名(参数列表)
1.全匹配方式:
public void com.yuxiang.service.impl.AccountServiceImpl.saveAccount()
2.访问修饰符可以省略:
void com.yuxiang.service.impl.AccountServiceImpl.saveAccount()
3.返回值可以使用*代替,表示任意符合值类型
* com.yuxiang.service.impl.AccountServiceImpl.saveAccount()
4.包名可以使用*代替,表示任意包;但是有几级包,需要写几个*
* *.*.*.*.AccountServiceImpl.saveAccount()
5.包名可以使用..代替,表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
6.类名可以使用*,表示任意类
* *..*.saveAccount()
7.方法名可以使用*代替,表示任意方法
* *..*.*()
8.参数类型可以指定具体类型
基本数据类型直接写类型名称:
* *..*.*(int)
引用类型必须写包名.类名:
* *..*.*(java.lang.String)
9.参数列表可以使用*代替,表示任意参数类型,但是必须有参数
* ..*.*(*)
10.参数列表可以使用..代替,表示有无参数均可
* *..*.*(..)
11.全通配
* *..*.*(..)
><font color =red>在实际的开发中,切入点表达式的使用一般都是对业务层的方法增强
* com.yuxiang,service,impl.*.*(..)
Java代码
基于xml配置文件
public interface IAccountService {
void saveAccount();
void updateAccount(int i);
void deleteAccount();
}
import com.yuxiang.service.IAccountService;
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
System.out.println("保存账号");
}
@Override
public void updateAccount(int i) {
System.out.println("更新账户");
}
@Override
public void deleteAccount() {
System.out.println("删除账户");
}
}
import org.aspectj.lang.ProceedingJoinPoint;
public class Logger {
public void beforePrintLog(){
System.out.println("Loggle类中的beforePrintLog方法开始记录日志 前置通知");
}
public void beforeReturningPrintLog(){
System.out.println("Loggle类中的beforePrintLog方法开始记录日志 后置通知");
}
public void beforeThrowingPrintLog(){
System.out.println("Loggle类中的beforePrintLog方法开始记录日志 异常通知");
}
public void afterPrintLog(){
System.out.println("Loggle类中的beforePrintLog方法开始记录日志 最终通知");
}
/**
* 环绕通知:他是spring提供的一种手动控制何时执行切入点方法的方式
* 配置spring的环绕通知后,在执行切入点方法时,只执行类环绕通知,切入点方法并没有执行,
* 在动态代理中,invoke方法中有一个明确调用切入点方法的代码,而spring的环绕通知中没有调用切入点方法;
* 所以在spring的环绕通知中,如果想在环绕通知执行时也执行切入点方法,就必须在环绕通知中明确调用切入点方法。
* 在spring框架中,ProceedingJoinPoint接口可以在环绕通知中通过proceed()方法来明确调用切入点方法。
*/
public void aroundPrintLog(ProceedingJoinPoint pjp){
System.out.println("Loggle类中的beforePrintLog方法开始记录日志 最终通知");
Object reValue = null;
try {
System.out.println("前置通知");
//获取方法所需的参数
Object[] args = pjp.getArgs();
//调用切入点方法
reValue = pjp.proceed(args);
System.out.println("后置通知");
} catch (Throwable e) {
System.out.println("异常通知");
e.printStackTrace();
}finally{
System.out.println("最终通知");
}
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
}
}
<?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-4.3.xsd">
<bean id="accountService" class="com.yuxiang.service.impl.AccountServiceImpl"></bean>
<!-- spring基于xml的aop配置步骤:
前期准备:
1.拷贝aopjar包
2.在配置文件中导入aop约束
配置步骤:
1.把通知bean也交给spring来管理
2.使用aop名称空间下的aop:config标签开始aop的配置
3.使用aop:aspect标签,开始配置切面
id:用于给切面提供唯一标识
ref:用于引用通知bean的id
4.使用aop:before标签配置前置通知
method:指定通知类中那个方法是前置通知
pointcut:用于指定切入点表达式
切入点表达式:
关键字:execution(表达式)
表达式写法:
访问修饰符 返回值 包名.包名...包名.类名.方法名(参数列表)
-->
<bean id="logger" class="com.yuxiang.utils.Logger"></bean>
<aop:config>
<!-- 使用aop:pointcut标签来定义切入点表达式,表示他的位置只能放在aop:aspect标签切面 -->
<aop:pointcut expression="execution(* com.yuxiang.service.impl.*.*(..))" id="mycut"/>
<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:他永远都会在切入点方法之前执行 -->
<!-- <aop:before method="beforePrintLog" pointcut="execution(* *..*.*())"/> -->
<!-- 配置后置通知:当切入点方法正常执行后,后置通知才会执行,并且他和异常通知是互斥的 -->
<!-- <aop:after-returning method="beforeReturningPrintLog" pointcut="execution(* *..*.*())"/> -->
<!-- 配置异常通知:当切入点方法执行异常时,异常通知才会执行,并且他与后置通知是互斥的 -->
<!-- <aop:after-throwing method="beforeThrowingPrintLog" pointcut="execution(* *..*.*())"/> -->
<!-- 配置最终通知:无论切入点方法是否正常执行,最终通知都会执行 -->
<!-- <aop:after method="afterPrintLog" pointcut="execution(* *..*.*())"/> -->
<!-- 配置环绕通知 -->
<aop:around method="aroundPrintLog" pointcut-ref="mycut" />
</aop:aspect>
<!-- 环绕通知一般不和其他通知结合使用,常单独使用-->
</aop:config>
</beans>
纯注解
public interface IAccountService {
void saveAccount();
void updateAccount(int i);
void deleteAccount();
}
import com.yuxiang.service.IAccountService;
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
System.out.println("保存账号");
}
@Override
public void updateAccount(int i) {
System.out.println("更新账户");
}
@Override
public void deleteAccount() {
System.out.println("删除账户");
}
}
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;
@Component("logger")
//把当前类配置成一个切面
@Aspect
public class Logger {
//配置切入点表达式
@Pointcut("execution(* com.yuxiang.service.impl.*.*(..))")
private void pt1(){};
@Before(value="pt1()")
public void beforePrintLog(){
System.out.println("Loggle类中的beforePrintLog方法开始记录日志 前置通知");
}
@AfterReturning(value="pt1()")
public void afterReturningPrintLog(){
System.out.println("Loggle类中的beforePrintLog方法开始记录日志 后置通知");
}
@AfterThrowing(value="pt1()")
public void afterThrowingPrintLog(){
System.out.println("Loggle类中的beforePrintLog方法开始记录日志 异常通知");
}
@After(value="pt1()")
public void afterPrintLog(){
System.out.println("Loggle类中的beforePrintLog方法开始记录日志 最终通知");
}
/**
* 环绕通知:他是spring提供的一种手动控制何时执行切入点方法的方式
* 配置spring的环绕通知后,在执行切入点方法时,只执行类环绕通知,切入点方法并没有执行,
* 在动态代理中,invoke方法中有一个明确调用切入点方法的代码,二spring的环绕通知中没有调用切入点方法;
* 所以在spring的环绕通知中,如果想在环绕通知执行时也执行切入点方法,就不行在环绕通知中明确调用切入点方法。
* 在spring框架中,ProceedingJoinPoint接口可以在环绕通知中通过proceed()方法来明确调用切入点方法。
*/
@Around(value="pt1()")
public void aroundPrintLog(ProceedingJoinPoint pjp){
System.out.println("Loggle类中的beforePrintLog方法开始记录日志 最终通知");
Object reValue = null;
try {
System.out.println("前置通知");
//获取方法所需的参数
Object[] args = pjp.getArgs();
//调用切入点方法
reValue = pjp.proceed(args);
System.out.println("后置通知");
} catch (Throwable e) {
System.out.println("异常通知");
e.printStackTrace();
}finally{
System.out.println("最终通知");
}
}
}
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
//指定当前类是spring的配置类
@Configuration
//指定创建容器时,要扫描的包
@ComponentScan("com.yuxiang")
//开启spring对注解AOP的支持
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
}
}