文章目录
AOP的全称是:Aspect Oriented Programming,也就是面向切面的编程(也叫面向方面的编程)。它是面向对象编程的(OOP)一种补充。OOP采取的是纵向的代码机制,所以只能实现父子关系的代码重用;而AOP是横向的代码机制,采取的是切入的方式执行代码。AOP是对OOP的一种补充,但并不是OOP的替代品。
怎么理解AOP呢?

这些执行的通用的内容可以是记录日志、开启事务。
JDK实现代理
jdk代理必须要实现InvocationHandler接口
package com.dz.aop_jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理类:LogHandler;实现InvocationHandler接口
*
*/
public class LogHandler implements InvocationHandler {
private Object targetObject; // 目标对象,或者是被代理类
/**
* 生成被代理类的对象
*
* @param targetObject
* @return
*/
public Object creatProxy(Object targetObject) {
this.targetObject = targetObject;
/*
* 创建一个被代理类 (参数 详情) -获取被代理类的类加载器 -获取被代理类的接口 -使用当前的代理类this来执行被代理的类
*/
return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
this.targetObject.getClass().getInterfaces(), this);
}
/**
* 代理类执行的方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result =null;
try {
//伪代码,这就是在执行方法之前横向切入的执行内容
System.out.println("准备~~开始记录日志...");
result = method.invoke(this.targetObject, args);//执行被代理类的方法
//伪代码:这是在方法执行之后横向切入的内容
System.out.println("日志记录完毕...");
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
创建被代理类UserDao.java
被代理的类必须要提供接口
package com.dz.aop_jdk;
public interface UserDao {
// 假装添加一个用户
void addUser();
}
实现接口
package com.dz.aop_jdk;
public class UserDaoImpl implements UserDao{
@Override
public void addUser() {
//伪代码
System.out.println("执行一个添加用户的操作...");
}
}
测试代理类
package com.dz.aop_jdk;
public class TestProxy {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
LogHandler log=new LogHandler();
//根据被代理的对象,生成代理的对象--根据指定的接口返回对象
UserDao proxyUser = (UserDao) log.creatProxy(userDao);
//让代理对象来执行方法
proxyUser.addUser();
}
}
jdk实现代理结果

SpringAOP的实现
流行的AOP框架目前有两个:SpringAOP和AspectJ。后来,从Spring2.0开始SpringAOP引入了对AspectJ的支持。并且SpringAOP建议使用AspectJ开发AOP。
开始AOP的配置之前,先来认识一下AOP中的一些术语:
- Aspect:切面。代理类就是切面。
- JoinPoint:连接点。方法的调用。
- PointCut:切入点。切面与程序流程的交叉点(比如执行到某一个类或者方法开始切入,这个切入的点就叫切入点)。
- Advince:通知。切入点处要执行的代码,可以理解为切面类中的方法。
- Target Object:目标对象。指被通知的对象,也就是被代理的对象。
- Proxy:代理。被动态创建的对象。
- Weaving:织入。将切面的代码插入到目标对象上。
通知又分为几个类型,下面在代码里面可以看到通知的类型。(也就是这个通知在方法的什么位置执行)。
xml配置方式实现SpringAOP
定义一个切面
package com.dz.aop_spring;
import org.aspectj.lang.ProceedingJoinPoint;
/**
*
* 配置一个切面类
*/
public class AspectXML {
public void beforeAdvice() {
System.out.println("前置通知..执行的方法,比如说:开启事务???");
}
public void afterAdvice() {
System.out.println("最终通知..执行的方法,比如说:关闭事务??");
}
public void afterReturnAdvice() {
System.out.println("后置通知");
}
public void afterThrowingAdvice() {
System.out.println("异常通知");
}
public Object aroundAdvice(ProceedingJoinPoint pjp) {
Object object = null;
try {
System.out.println("环绕通知开始...");
object = pjp.proceed();// 执行切点,切中的方法
System.out.println("环绕通知结束...");
} catch (Throwable e) {
e.printStackTrace();
}
return object;
}
}
定义切点切入的类
package com.dz.aop_spring;
public interface UserService {
public void insertUser();
}
package com.dz.aop_spring;
public class UserServiceImpl implements UserService {
//假装插入一条数据
@Override
public void insertUser() {
System.out.println("执行一个插入用户的操作");
}
}
配置切面
<?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-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd ">
<bean id="userService" class="com.dz.aop_spring.UserServiceImpl"></bean>
<!--配置切面 -->
<bean id="aspectXmL" class="com.dz.aop_spring.AspectXML"></bean>
<!-- 配置切面 -->
<aop:config>
<aop:aspect id="myAspect" ref="aspectXmL">
<!-- 配置切入点 :根据表达式找切入点,满足切入点执行通知-->
<aop:pointcut
expression="execution(* com.dz.aop_spring.*.*(..))" id="myCut" />
<!-- 根据切入点,执行一些类型的通知方法 -->
<aop:before method="beforeAdvice" pointcut-ref="myCut" />
<aop:after method="afterAdvice" pointcut-ref="myCut"/>
<aop:after-returning method="afterReturnAdvice" pointcut-ref="myCut" />
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="myCut" />
<aop:around method="aroundAdvice" pointcut-ref="myCut"/>
</aop:aspect>
</aop:config>
</beans>
测试类
package com.dz.aop_spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//满足切入点的类,和类下面所有的方法
UserService userService = ac.getBean("userService", UserService.class);
userService.insertUser();
}
}
结果

异常通知在方法出现异常的时候才会执行。
注解配置SpringAOP
注解配置AOP也是要声明切面的!!!
package com.dz.aop_anno;
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;
/**
*
* 配置一个切面类
*/
@Aspect // 注解声明切面
//@Component //注解声明需要一个扫描
public class AspectAnno {
// 定义切入点:任何返回类型,aop_anno包下的任何类下的任何方法包括任意参数都满足切入点
@Pointcut("execution(* com.dz.aop_anno.*.*(..))")
public void pointCut() {
}
//这里也可以直接将表达式放进去
@Before(value = "execution(* com.dz.aop_anno.*.*(..))")
//@Before("pointCut()") // 根据切点切入通知
public void beforeAdvice() {
System.out.println("注解:前置通知..执行的方法,比如说:开启事务???");
}
@After("pointCut()")
public void afterAdvice() {
System.out.println("最终通知..执行的方法,比如说:关闭事务??");
}
@AfterReturning("pointCut()")
public void afterReturnAdvice() {
System.out.println("后置通知");
}
@AfterThrowing("pointCut()")
public void afterThrowingAdvice() {
System.out.println("异常通知");
}
@Around("pointCut()")
public Object aroundAdvice(ProceedingJoinPoint pjp) {
Object object = null;
try {
System.out.println("环绕通知开始...");
object = pjp.proceed();// 执行切点,切中的方法
System.out.println("环绕通知结束...");
} catch (Throwable e) {
e.printStackTrace();
}
return object;
}
}
定义一个UserService.java接口
package com.dz.aop_anno;
public interface UserService {
public void updatetUser();
}
并实现它
package com.dz.aop_anno;
public class UserServiceImpl implements UserService {
//假装修改一条数据
@Override
public void updatetUser() {
System.out.println("执行一个修改用户的操作");
}
}
配置文件开启注解
<?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-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/context ">
<!-- 这里没有使用扫描,而是直接装配的bean -->
<bean id="userService" class="com.dz.aop_anno.UserServiceImpl"></bean>
<bean id="aspectAnno" class="com.dz.aop_anno.AspectAnno"></bean>
<!--启动注解配置切入方式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
测试注解配置的AOP
package com.dz.aop_anno;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.updatetUser();
}
}
结果

咦???仔细对比了两张结果图发现不一样?
总结
- 两张结果图可以看出:
基于XML与注解的方式执行的结果是相同的,只是目标方法前后的通知顺序发生了改变。
- 需要注意的是:
如果同一个连接点有多个通知执行,那么前置通知与环绕通知的执行顺序未知,后置通知与环绕通知的执行顺序未知。
- xml配置与注解配置的相同之处
无论使用的哪种方法的配置,都需要定义:切面、切点、通知。(除非有人帮你定义好了,你只需要引用。比如:事务管理。)
附上我的目录结构

依赖

1925

被折叠的 条评论
为什么被折叠?



