在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在业务层里,每个增删改方法,都要对事务进行操作, 即执行增删改前要开启事务,发生异常要回滚事务,没有异常则提交事务等编码操作,这样就会有很多的重复的代码,而使用AOP(实现方式:动态代理技术),则可以把重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
对于在所有方法执行的前后,加入日志的输出这类需求,原有的方法是没有这个功能的,这时就可以考虑对方法进行增强,原本方法的代码不变(这里只编写业务代码),而需要增强的功能(日志输出),则在需要执行的时候,使用动态代理的技术,在原本方法执行前后执行 输出日志记录方法 即可。
Java的JDK动态代理与CGLib动态代理
框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式
Joinpoint
(连接点):连接点是指那些被拦截到的点。在spring中,这些点指的是方法,spring只支持方法类型的连接点。Pointcut
(切入点):切入点是指我们要对哪些Joinpoint进行拦截的定义。Advice
(通知/增强):通知是指拦截到Joinpoint之后所要做的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。Introduction
(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。Target
(目标对象):代理的目标对象。Weaving
(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。Proxy
(代理):一个类被AOP织入增强后,就产生一个结果代理类。Aspect
(切面): 是切入点和通知(引介)的结合。配置案例:调用方法时加入日志的输出。
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.2.RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.7version>
dependency>
//用户业务层接口
public interface UserService {
//根据id查询用户
String getUserNameId(Integer id);
void deleteUser(Integer id);
}
//用户业务层实现类
public class UserServiceImpl implements UserService {
public String getUserNameId(Integer id) {
System.out.println("执行UserServiceImpl getUserNameId方法");
// Object a=2/0;
return "张三三";
}
public void deleteUser(Integer id) {
System.out.println("执行UserServiceImpl deleteUser方法");
}
}
//模拟的记录日志的工具类
public class Logger {
//指定方法调用前执行
public void beforeLog(){
System.out.println("=====前置通知执行======");
}
//指定方法调用之后执行
public void afterrturningLog(){
System.out.println("=====后置通知执行======");
}
// 异常时,异常通知
public void afterThrowing(){
System.out.println("====异常通知执行=====");
}
//finally时,最终通知
public void after(){
System.out.println("=====最终通知执行====");
}
/***
环绕通知
参数: ProceedingJoinPoint,被调用方法的签名对象
ProceedingJoinPoint接口可以作为环绕通知的方法参数来使用。当环绕通知执行时,spring框架会为我们注入该接口的实现类。
ProceedingJoinPoint有一个方法proceed(),相当于invoke,调用目标方法(写法类似java动态代理的invoke方法)
*/
public Object around(ProceedingJoinPoint jp){
Object result = null;
try {
System.out.println("=====环绕通知开始执行======");
//前置通知
System.out.println("=====前置通知执行======");
//调用目标方法
result = jp.proceed();
//执行类似后置通知
System.out.println("=====后置通知执行======");
} catch (Throwable throwable) {
//异常通知
System.out.println("====异常通知执行=====");
throwable.printStackTrace();
}finally {
//最终通知
System.out.println("=====最终通知执行====");
}
return result;
}
}
<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">
<bean id="userService" class="com.mycode.service.impl.UserServiceImpl">bean>
<bean id="logger" class="com.mycode.util.Logger">bean>
<aop:config>
<aop:pointcut id="pointLog" expression="execution(* com..*.*(..))" />
<aop:aspect id="log" ref="logger">
<aop:before method="beforeLog" pointcut="execution(* com..*.*(..))">aop:before>
<aop:after-returning method="afterrturningLog" pointcut-ref="pointLog">aop:after-returning>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointLog">aop:after-throwing>
<aop:after method="after" pointcut-ref="pointLog">aop:after>
<aop:around method="around" pointcut-ref="pointLog">aop:around>
aop:aspect>
aop:config>
beans>
public class SpringAOPTest {
private UserService userService;
@Before
public void init() {
//创建Spring容器对象ApplicationContext
ApplicationContext act = new ClassPathXmlApplicationContext("beans.xml");
//根据bean的id获取userService
userService = (UserService) act.getBean("userService");
}
@Test
public void SelectTest(){
String name = userService.getUserNameId(1);
System.out.println(name);
System.out.println("----------------------------");
userService.deleteUser(2);
}
}
配置案例:调用方法时加入日志的输出。
//模拟的记录日志的工具类
@Component("logger") //创建Logger对象的实例,并存入SpringIOC容器中,给SpringIOC容器管理
@Aspect //在通知类上使用@Aspect注解声明为切面,表示当前类是一个切面类
public class Logger {
//切入点表达式定义(切入点表达式注解)
@Pointcut("execution(* com..*.*(..))")
public void logPoint(){}
/* @Before
把当前方法看成是前置通知。
value属性:用于指定切入点表达式,也可以指定切入点表达式的引用。
*/
@Before("logPoint()")//logPoint()指定切入点表达式的引用
public void beforeLog(){
System.out.println("=====前置通知执行======");
}
//把当前方法看成是后置通知。
@After("execution(* com..*.*(..))")//value指定的是切入点表达式
public void afterrturningLog(){
System.out.println("=====后置通知执行======");
}
//把当前方法看成是异常通知
@AfterThrowing("logPoint()")
public void afterThrowing(){
System.out.println("====异常通知执行=====");
}
//把当前方法看成是最终通知。
@After("logPoint()")
public void after(){
System.out.println("=====最终通知执行====");
}
/***
环绕通知
参数: ProceedingJoinPoint,被调用方法的签名对象
ProceedingJoinPoint接口可以作为环绕通知的方法参数来使用。当环绕通知执行时,spring框架会为我们注入该接口的实现类。
ProceedingJoinPoint有一个方法proceed(),相当于invoke,调用目标方法
*/
@Around("logPoint()")
public Object around(ProceedingJoinPoint jp){
Object result = null;
try {
System.out.println("=====环绕通知开始执行======");
//前置通知
System.out.println("=====前置通知执行======");
//调用目标方法
result = jp.proceed();
//执行类似后置通知
System.out.println("=====后置通知执行======");
} catch (Throwable throwable) {
//异常通知
System.out.println("====异常通知执行=====");
throwable.printStackTrace();
}finally {
//最终通知
System.out.println("=====最终通知执行====");
}
return result;
}
}
@Configuration //指定当前类是一个spring配置类
@ComponentScan(basePackages = "com.mycode") //指定spring在初始化容器时要扫描的包。
@EnableAspectJAutoProxy //开启spring对注解AOP的支持
public class SpringConfig {
}
需要在spring配置文件中开启spring对注解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">
<!--包扫描-->
<context:component-scan base-package="com.mycode" />
<!--开启spring对注解AOP的支持-->
<aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
表达式语法:execution(修饰符 返回值类型 包名.类名.方法名(参数))
写法:
全匹配方式:
public void com.mycode.service.impl.UserServiceImpl.add(com.mycode.domain.User)
省略修饰符方式:
void com.mycode.service.impl.UserServiceImpl.add(com.mycode.domain.User)
返回值使用*号代替,表示任意返回值
* com.mycode.service.impl.UserServiceImpl.add(com.mycode.domain.User)
包名使用*号代替,表示任意包。有几级包,写几个*
* *.*.*.*.UserServiceImpl.add(com.mycode.domain.User)
使用..来表示当前包及其子包
* com..UserServiceImpl.add(com.mycode.domain.User)
使用*号表示任意类
* com..*.add(com.mycode.domain.User)
使用*号表示任意方法
* com..*.*( com.mycode.domain.User)
参数列表使用*代替,表示参数可以是任意数据类型,但是必须有参数
* com..*.*(*)
参数列表使用..代替表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
全通配方式:
* *..*.*(..)
了解:
通常情况下,都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类,写法:
execution(* com.mycode.service.impl.*.*(..))