AOP(Aspect Oriented Programming):面向切面编程
一、AOP的思想
正常的传统的程序执行流程都是纵向执行流程,AOP(面向切面编程)在原有的纵向执行流程中添加横切面。
二、AOP的优点
1、AOP的使用不需要修改原有程序代码,
2、具有高扩展性,
3、原有的功能相当于释放了部分逻辑,让职责更加明确。
三、面向切面编程到底是什么?
在程序原有纵向执行流程中,针对某个或某一些方法添加通知,形成横切面过程就叫做面向切面编程
四、常用的概念
编号2:切点 需要添加额外功能的方法,
编号3:前置通知 在切点之前执行的功能,before advice,
编号4:后置通知 在切点之后执行的功能,after advice,
异常通知: 如果切点执行过程中出现异常,会触发异常通知 throws advice,
编号1:切面 所有功能总称为切面,
织入: 把切面嵌入到原有功能的过程叫做织入。
五、spring 提供了2种AOP实现方式
1、Schema-based
(1)每一个通知都需要实现接口或者类,
(2)配置spring配置文件时,切面在aop:config标签下配置。
2、AspectJ
(1)每个通知不需要实现接口或类,
(2)配置spring配置文件时切面在aop:config的子标签aop:aspect下配置。
六、实现
方式一:Schema-based方式
1、导包
aopalliance-1.0.jar
aspectjrt-1.8.10.jar
aspectjweaver-1.8.10.jar
commons-logging-1.1.1.jar
spring-aop-4.3.10.RELEASE.jar
spring-beans-4.3.10.RELEASE.jar
spring-context-4.3.10.RELEASE.jar
spring-core-4.3.10.RELEASE.jar
spring-expression-4.3.10.RELEASE.jar
2.新建通知类
2.1 新建前置通知类
2.1.1 arg0 切点方法对象 Method 对象
2.1.2 arg1 切点方法参数
2.1.3 arg2 切点在哪个对象中
public class MyBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2){
System.out.println("执行前置通知");
}
}
2.2 新建后置通知类
2.2.1 arg0 切点方法返回值
2.2.2 arg1 切点方法对象
2.2.3 arg2 切点方法参数
2.2.4 arg3 切点方法所在类的对象
public class MyAfterAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1,Object[] arg2, Object
arg3){
System.out.println("执行后置通知");
}
}
3.配置 spring 配置文件
3.1 引入 aop 命名空间
3.2 配置通知类的
3.3 配置切面
3.4 * 通配符,匹配任意方法名,任意类名,任意一级包名
3.5 如果希望匹配任意方法参数 (…)
<?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/sc hema/beans
http://www.springframework.org/schema/beans/spring-be ans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop. xsd">
<!-- 配置通知类对象,在切面中引入 -->
<bean id="mybefore" class="com.woniuxy.advice.MyBeforeAdvice"></bean>
<bean id="myafter" class="com.woniuxy.advice.MyAfterAdvice"></bean>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切点 -->
<aop:pointcut expression="execution(* com.woniuxy.test.Demo.demo2())"
id="mypoint" />
<!-- 通知 -->
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint" />
<aop:advisor advice-ref="myafter" pointcut-ref="mypoint" />
</aop:config>
<!-- 配置 Demo 类,测试使用 -->
<bean id="demo" class="com.woniuxy.test.Demo"></bean>
</beans>
4.编写测试代码
public class Test {
public static void main(String[] args){
// Demo demo = new Demo();
// demo.demo1();
// demo.demo2();
// demo.demo3();
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = ac.getBean("demo", Demo.class);
demo.demo1();
demo.demo2();
demo.demo3();
}
}
通知类型:
前置通知
后置通知
环绕通知
异常通知
返回后通知
方式二:Aspectj方式
1、IUserDao
//IUserService接口
public interface IUserService {
void save();
}
2、UserService实现类(实现接口)
UserDao实现类(有实现接口)
@Component // 加入容器
public class UserServiceImpl implements IUserService{
@Override
public void save() {
System.out.println("-----调用DAO核心业务:保存!!!------");
}
}
3、切面类
public class Aop {
public void begin() {
System.out.println("开始事务/异常");
}
public void after() {
System.out.println("无论程序正常执行还是有异常,只要程序执行完就会执行这个通知");
}
public void afterReturning() {
System.out.println("程序执行到return,代表程序正常执行完毕,所以只有程序正常执行完,没有异常才会执行这个通知");
}
public void afterThrowing() {
System.out.println("afterThrowing()");
}
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前....");
pjp.proceed(); // 执行目标方法
System.out.println("环绕后....");
}
}
4、spring-context.xml
<!-- dao实例 -->
<bean id="userService" class="com.my.g_aop_xml.UserService"></bean>
<bean id="otherService" class="com.my.g_aop_xml.OtherService"></bean>
<!-- 切面类 -->
<bean id="aop" class="com.my.g_aop_xml.Aop"></bean>
<!-- AOP配置 -->
<aop:config>
<!-- 定义一个切入点表达式 -->
<aop:pointcut expression="execution(* com.my.g_aop_xml.*.*(..))" id="pt"/>
<!-- 配置切面 -->
<aop:aspect ref="aop">
<!-- 前置通知 -->
<aop:before method="begin" pointcut-ref="pt"/>
<!-- 后置通知 -->
<aop:after method="after" pointcut-ref="pt"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pt"/>
<!-- 返回后通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
5、测试类
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/my/f_aop_anno/bean.xml");
//目标对象有实现接口,spring会默认采用JDK代理
IUserService userService = (IUserService) ac.getBean("userService");
System.out.println(userService.getClass());//$Proxy001
userDao.save();
}
注解方式实现Aspectj
1、IUserService接口
public interface IUserService {
void save();
}
2、UserDao实现类(实现接口)
@Component // 加入容器
public class UserServiceImpl implements IUserService{
@Override
public void save() {
System.out.println("-----调用DAO核心业务:保存!!!------");
}
}
3、AOP切面类
@Component // 加入IOC容器
@Aspect // 指定当前类为切面类
public class Aop {
// 指定切入点表达式:拦截哪些方法(为哪些类生成代理对象)
@Pointcut("execution(* com.my.f_aop_anno.*.*(..))")
public void pointCut_() {
}
//JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就
//可以获取到封装了该方法信息的JoinPoint对象
@Before("pointCut_()")
public void begin(JoinPoint point) {
System.out.println("开始事务/异常");
//方法签名获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class
//等信息
MethodSignature methodSignature =
(MethodSignature)point.getSignature();
System.out.println("调用"+methodSignature.getMethod().getName()+"方法");
}
// 后置/最终通知:在执行目标方法之后执行 【无论是否出现异常最终都会执行】
@After("pointCut_()")
public void after() {
System.out.println("提交事务/关闭");
}
// 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】
@AfterReturning("pointCut_()")
public void afterReturning() {
System.out.println("afterReturning()");
}
// 异常通知: 当目标方法执行异常时候执行此关注点代码
@AfterThrowing("pointCut_()")
public void afterThrowing() {
System.out.println("afterThrowing()");
}
// 环绕通知:环绕目标方式执行
//ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中
@Around("pointCut_()")
public void around(ProceedingJoinPoint pjp) {
System.out.println("环绕前....");
try{
pjp.proceed(); // 执行目标方法
}catch (Throwable exception){
}finaly{
}
System.out.println("环绕后....");
}
}
4.4、bean.xml配置文件
<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: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/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.my.f_aop_anno"></context:component-scan>
<!-- 开启aop注解方式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
4.5、测试类
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/my/f_aop_anno/bean.xml");
//目标对象有实现接口,spring会默认采用JDK代理
IUserService userService =
(IUserService) ac.getBean("userDaoServiceImpl");
System.out.println(userServiceImpl.getClass());//$Proxy001
userServiceImpl.save();
//目标对象没有实现接口,spring默认采用cglib代理
OtherServiceImpl otherServiceImpl =
(OtherServiceImpl)ac.getBean("otherServiceImpl");
System.out.println(otherServiceImpl.getClass());
otherImpl.save();
}
4.6、OtherService类(未实现接口)
@Component
public class OtherServiceImpl {
public void save() {
System.out.println("执行没有接口的dao");
}
}
补充:aop是基于代理模式实现的,spring中本身就嵌入了CGLIB,当包含切点的类实现了接口时,aop默认使用JDK代理;当包含切点的类没有实现接口那么aop就使用CGLIB代理。