Aop切面编程概念
AOP切面编程一般可以帮助我们在不修改现有代码的情况下,对程序的功能进行拓展,往往用于实现 日志处理,权限控制,性能检测,事务控制等
AOP实现的原理就是动态代理,在有接口的情况下,使用JDK动态代理,在没有接口的情况下使用cglib动态代理
为Dao层所有的add方法添加一个性能记录功能(抽象化横切面)
AOP中的术语辨析
1.连接点 Joint point:
类里面那些可以被增强的方法,这些方法称之为连接点
表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point
2.切入点 Pointcut:
实际被增强的方法,称之为切入点
表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方
3.通知 Advice:
实际增强的逻辑部分称为通知 (增加的功能)
Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
通知类型: 1 前置通知 2 后置通知 3 环绕通知 4 异常通知 5 最终通知
4.目标对象 Target:
被增强功能的对象(被代理的对象)织入 Advice 的目标对象
5.切面Aspect:
表现为功能相关的一些advice方法放在一起声明成的一个Java类
Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
6.织入 Weaving:
创建代理对象并实现功能增强的声明并运行过程
将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
Spring Aop实现
AspectJ本身并不是spring框架中的组成部分, 是一个独立的AOP框架,一般把AspectJ和Spring框架的AOP依赖一起使用,所以要导入一个独立的依赖
实现的两种方式
1.基于注解方式实现 (熟练)
2.基于XML配置方式 (了解)
准备工作
导入依赖
<dependencies>
<!--spring核心容器包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.5</version>
</dependency>
<!--spring切面包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.5</version>
</dependency>
<!--织入包 spring-aspects 已经导入该包,这里可以不导入-->
<!--<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>-->
<!--aop联盟包-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!--Apache Commons日志包-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!--德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!--Junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!--lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
切入点表达式
切入点表达式: 通过一个表达式来确定AOP要增强的是哪个或者那些方法
语法结构:execution([权限修饰符][返回值类型][类的全路径名][方法名](参数 列表) )
例子1
execution(* com.msb.dao.UserDaoImpl.add(..)) //指定切点为UserDaoImpl.add方法
execution(* com.msb.dao.UserDaoImpl.*(..)) //指定切点为UserDaoImpl.所有的方法
execution(* com.msb.dao.*.*(..)) //指定切点为dao包下所有的类中的所有的方法
execution(* com.msb.dao.*.add(..)) // 指定切点为dao包下所有的类中的add的方法
execution(* com.msb.dao.*.add*(..)) // 指定切点为dao包下所有的类中的add开头的方法
项目结构
基于注解方式实现
开启注解扫描和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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
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/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--spring 包扫描 -->
<context:component-scan base-package="com.msb"/>
<!--aop autoProxy 自动生成代理对象 -->
<aop:aspectj-autoproxy/>
</beans>
准备接口
UserDao和EmpDao
package com.msb.dao;
/**
* @Author: Ma HaiYang
* @Description: MircoMessage:Mark_7001
*/
public interface EmpDao {
int addEmp(Integer empno,String ename,String job);
}
package com.msb.dao;
/**
* @Author: Ma HaiYang
* @Description: MircoMessage:Mark_7001
*/
public interface UserDao {
int addUser(Integer userid,String username);
}
接口实现类
package com.msb.dao.impl;
import com.msb.dao.UserDao;
import org.springframework.stereotype.Repository;
/**
* @Author: Ma HaiYang
* @Description: MircoMessage:Mark_7001
*/
@Repository
public class UserDaoImpl implements UserDao {
public int addUser(Integer userid,String username){
System.out.println("userdao add ... ...");
//int i =1/0;
return 1;
}
}
package com.msb.dao.impl;
import com.msb.dao.EmpDao;
import com.msb.dao.UserDao;
import org.springframework.stereotype.Repository;
/**
* @Author: Ma HaiYang
* @Description: MircoMessage:Mark_7001
*/
@Repository
public class EmpDaoImpl implements EmpDao {
public int addEmp(Integer empno,String ename,String job){
System.out.println("empDao add ... ...");
return 1;
}
}
准备切面
package com.msb.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @Author: Ma HaiYang
* @Description: MircoMessage:Mark_7001
*/
@Component
@Aspect
public class DaoAspect {
//定义公共切点
@Pointcut("execution(* com.msb.dao.*.add*(..))")
public void addPointCut(){}
/*
* 前置通知: 切点方法执行之前先执行的功能
* 参数列表可以用JoinPoint接收切点对象
* 可以获取方法执行的参数
* */
@Before("addPointCut()")
public void methodBefore(JoinPoint joinPoint){
System.out.println("Before invoked");
}
/*
* 后置通知:方法执行之后要增强的功能
* 无论切点方法是否出现异常都会执行的方法
* 参数列表可以用JoinPoint接收切点对象
* */
@After("addPointCut()")
public void methodAfter(JoinPoint joinPoint){
System.out.println("After invoked");
}
/*
* 返回通知:切点方法正常运行结束后增强的功能
* 如果方法运行过程中出现异常,则该功能不运行
* 参数列表可以用 JoinPoint joinPoint接收切点对象
* 可以用Object res接收方法返回值,需要用returning指定返回值名称
* */
@AfterReturning( value = "addPointCut()",returning = "res")
public void methodAfterReturning(JoinPoint joinPoint,Object res){
System.out.println("AfterReturning invoked");
}
/*
* 异常通知:切点方法出现异常时运行的增强功能
* 如果方法运行没有出现异常,则该功能不运行
* 参数列表可以用Exception ex接收异常对象 需要通过throwing指定异常名称
* */
@AfterThrowing( value = "addPointCut()",throwing = "ex")
public void methodAfterThrowing(Exception ex){
System.out.println("AfterThrowing invoked");
}
/*环绕通知:在切点方法之前和之后都进行功能的增强
* 需要在通知中定义方法执行的位置,并在执行位置之前和之后自定义增强的功能
* 方法列表可以通过ProceedingJoinPoint获取执行的切点
* 通过proceedingJoinPoint.proceed()方法控制切点方法的执行位置
* proceedingJoinPoint.proceed()方法会将切点方法的返回值获取到,并交给我们,可以做后续处理
* 我们在环绕通知的最后需要将切点方法的返回值继续向上返回,否则切点方法在执行时接收不到返回值
* */
@Around("addPointCut()")
public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("aroundA invoked");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("aroundB invoked");
return proceed;
}
}
测试代码
@Test
public void test1(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = context.getBean( UserDao.class);
int add = userDao.addUser(10,"晓明");
}
JoinPoint对象和ProceedingJoinPoint对象
有多个增强类对同一个方法进行增强,通过@Order注解设置增强类优先级
数字越小,优先级越高
数字越大,其代理位置越靠近注入位置
完全使用注解开发
创建配置类替代applicationContext.xml
package com.msb.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @Author: Ma HaiYang
* @Description: MircoMessage:Mark_7001
*/
@Configuration
@ComponentScan(basePackages = "com.msb")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
}
测试代码
@Test
public void test2(){
ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
UserDao userDao = context.getBean( UserDao.class);
int add = userDao.addUser(10,"晓明");
}
基于XML配置方式
取消上面配置的切面类注解
package com.msb.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//@Aspect
//@Component
//@Order(2)
public class DaoAspect {
//定义一个公共切点,通过切点表达式来动态控制需要增强的方法
// @Pointcut("execution(* com.msb.dao.*.add*(..))")
public void addPointCut(){};
// @Before("addPointCut()")
public void methodBefore(JoinPoint joinPoint) {
System.out.println("前置通知");
// Object[] args = joinPoint.getArgs();
// System.out.println("args = " + Arrays.toString(args));
}
// @After("addPointCut()")
public void methodAfter(JoinPoint joinPoint) {
System.out.println("后置通知");
}
// @AfterReturning(value = "addPointCut()", returning = "res")
public void methodAfterReturning(JoinPoint joinPoint, Object res) {
System.out.println("返回通知" + res);
}
// @AfterThrowing(value = "addPointCut()", throwing = "exception")
public void methodAfterThrowing(Exception exception) {
System.out.println("异常通知" + exception);
}
// @Around("addPointCut()")
public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知A");
Object res = proceedingJoinPoint.proceed();
System.out.println("环绕通知B");
return res;
}
}
配置文件配置如下内容
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
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/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- <!– spring 包扫描–>-->
<!-- <context:component-scan base-package="com.msb"/>-->
<!-- <!– aop autoProxy 自动生成代理对象–>-->
<!-- <aop:aspectj-autoproxy/>-->
<!--创建对象-->
<bean id="userDao" class="com.msb.dao.impl.UserDaoImplA"></bean>
<bean id="daoAspect" class="com.msb.aspect.DaoAspect"></bean>
<!--配置aop增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointCutAdd" expression="execution(* com.msb.dao.*.add*(..))"/>
<!--配置切面-->
<aop:aspect ref="daoAspect">
<!--增强作用在具体的方法上-->
<aop:before method="methodBefore" pointcut-ref="pointCutAdd"/>
<aop:after method="methodAfter" pointcut-ref="pointCutAdd"/>
<aop:around method="methodAround" pointcut-ref="pointCutAdd"/>
<aop:after-returning method="methodAfterReturning" pointcut-ref="pointCutAdd" returning="res"/>
<aop:after-throwing method="methodAfterThrowing" pointcut-ref="pointCutAdd" throwing="exception"/>
</aop:aspect>
</aop:config>
</beans>
测试代码
@Test
public void getBean() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = context.getBean(UserDao.class);
int res = userDao.addUser("aaa", "张三");
System.out.println("res = " + res);
}
配置成功,打印如下
前置通知
环绕通知A
UserDaoImplA add ...
返回通知1
环绕通知B
后置通知
res = 1