03.AOP
1. 底层原理
- 面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗来说就是在不修改代码的情况下添加新的功能。
- 底层通过动态代理来实现:
- 第一种:有接口的情况,使用 JDK 动态代理:创建接口实现类的代理对象。
- 第二种:无接口的情况,使用 CGLIB 动态代理:创建当前类子类的代理对象。
JDK 动态代理举例:
- 通过 java.lang.reflect.Proxy 类 的 newProxyInstance 方法 创建代理类。
- newProxyInstance 方法:
参数一:类加载器 参数二:所增强方法所在的类,这个类实现的接口,支持多个接口 参数三:实现 InvocationHandle 接口,重写 invoke 方法来添加新的功能
代码举例:
java
public interface UserDao {
public int add(int a, int b);
public int multi(int a, int b);
}
java
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public int multi(int a, int b) {
return a*b;
}
}
java
public class Main {
@Test
public void test1(){
//所需代理的类实现的接口,支持多个接口
Class[] interfaces = {UserDao.class};
UserDao userDao = new UserDaoImpl();
//调用newProxyInstance方法来创建代理类
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(Main.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = userDaoProxy.add(1, 2);
System.out.println(result);
}
//创建内部类,实现InvocationHandler接口,重写invoke方法,添加新功能
class UserDaoProxy implements InvocationHandler {
Object obj;
//通过有参构造函数将所需代理的类传过来
public UserDaoProxy(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入" + method.getName() + "方法,这是新增的代码,参数有" + Arrays.toString(args));
//执行原有的代码
Object invoke = method.invoke(obj, args);
System.out.println("方法原先的内容执行完了");
return invoke;
}
}
}
2.基于 AspectJ 实现 AOP 操作
2.1 AOP 相关术语
- 连接点:类中可以被增强的方法,称为连接点。
- 切入点:实际被增强的方法,称为切入点。
- 通知:增强的那一部分逻辑代码。通知有多种类型:
- 前置通知:增强部分代码在原代码前面。
- 后置通知:增强部分代码在原代码后面。
- 环绕通知:增强部分代码既有在原代码前面,也有在原代码后面。
- 异常通知:原代码发生异常后才会执行。
- 最终通知:类似与 finally 那一部分
- 切面:指把通知应用到切入点这一个动作。
2.2 基于 AspectJ 实现 AOP 有两种方式
- 基于 xml 配置文件
- 基于注解方法
2.3 切入点表达式
- 语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表])
- 举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
java
execution(* com.auguigu.dao.BookDao.add(..))
- 举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
java
execution(* com.atguigu.dao.BookDao.*(..))
- 举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
java
execution(* com.atguigu.dao.*.* (..))
3.基于注解方式
java
@Component
public class User {
public void add(){
System.out.println("User.add()");
}
}
java
@Component
@Aspect //使用Aspect注解
public class UserProxy {
//前置通知
@Before(value="execution(* com.oymn.spring5.User.add(..))")
public void before(){
System.out.println("UserProxy.before()");
}
//后置通知
@AfterReturning(value="execution(* com.oymn.spring5.User.add(..))")
public void afterReturning(){
System.out.println("UserProxy.afterReturning()");
}
//最终通知
@After(value="execution(* com.oymn.spring5.User.add(..))")
public void After(){
System.out.println("UserProxy.After()");
}
//异常通知
@AfterThrowing(value="execution(* com.oymn.spring5.User.add(..))")
public void AfterThrowing(){
System.out.println("UserProxy.AfterThrowing()");
}
//环绕通知
@Around(value="execution(* com.oymn.spring5.User.add(..))")
public void Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("UserProxy.Around() _1");
//调用proceed方法执行原先部分的代码
proceedingJoinPoint.proceed();
System.out.println("UserProxy.Around() _2");
}
}
配置 xml 文件:
xml
<!--开启组件扫描-->
<context:component-scan base-package="com.oymn"></context:component-scan>
<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
测试类:
java
@Test
public void test2(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}
运行结果:
运行结果中没有出现异常通知,在 add 方法中添加int i = 1/0;
java
public void add(){
int i = 1/0;
System.out.println("User.add()");
}
运行结果:从这里也可以看到,但出现异常时,After 最终通知有执行,而 AfterReturning 后置通知并没有执行。
对于上面的例子,有很多通知的切入点都是相同的方法,因此,可以将该切入点进行抽取:通过@Pointcut 注解
java
@Pointcut(value="execution(* com.oymn.spring5.User.add(..))")
public void pointDemo(){
}
//前置通知
@Before(value="pointDemo()")
public void before(){
System.out.println("UserProxy.before()");
}
设置增强类优先级:
当有多个增强类对同一方法进行增强时,可以通过**@Order(数字值)来设置增强类的优先级,数字越小优先级越高。**
java
@Component
@Aspect
@Order(1)
public class PersonProxy
完全注解开发 :
可以通过配置类来彻底摆脱 xml 配置文件:
java
@Configuration
@ComponentScan(basePackages = "com.oymn.spring5")
//@EnableAspectJAutoProxy注解相当于上面xml文件中配置的 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Config {
}
4.基于 xml 方式
这种方式开发中不怎么用,了解即可。
创建 Book 和 BookProxy 类
java
public class Book {
public void buy(){
System.out.println("buy()");
}
}
java
public class BookProxy {
public void before(){
System.out.println("before()");
}
}
配置 xml 文件:
xml
<!--创建对象-->
<bean id="book" class="com.oymn.spring5.Book"></bean>
<bean id="bookProxy" class="com.oymn.spring5.BookProxy"></bean>
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.oymn.spring5.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<aop:before method="before" pointcut-ref="p"/> <!--将bookProxy中的before方法配置为切入点的前置通知-->
</aop:aspect>
</aop:config>