什么是AOP
AOP详解(AOP概览),AOP面试
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
[
](https://blog.csdn.net/dadiyang/article/details/82920139)
(1):aop-面向切面(方面)编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
(2):通俗易懂描述,不通过修改源代码方式,在主干功能中添加新代码进行实现新功能。
(3):AOP实例
AOP(底层原理)
1:底层使用动态代理,什么是代理?什么是动态代理?
有俩种情况动态代理
1:有接口情况——JDK动态代理(创建接口实现类代理对象,增强类的方法)
2:没有接口情况——使用CGLIB动态代理(创建子类的代理对象,来增强类的方法)
JDK动态实现实现
1:使用jdk动态代理,需要使用Proxy类里的方法来创建代理对象,jdkApi
(1):调用 java.lang.Object java.lang.reflect.Proxy中的newProxyInstance)
newProxyInstance) 方法有三个参数
1:第一个参数,类加载器
2:第二个参数:增强方法所在的类,这个类实现的接口,支持多接口
3:第三个参数:实现这个接口 InvocationHandler ,创建代理对象,主要是具体增强方法的内容
2:编写JDK动态代理,具体实现
(1):创建接口,定义方法
package com.junjay.spring5.dao;
/**
* @author My
* interface 接口关键字
*/
public interface UserDao{
public int add(int a,int b);
public String del(String id);
}
(2):创建接口实现类,实现改接口
package com.junjay.spring5.dao;
import org.springframework.stereotype.Repository;
/**
* @author My
* implements 实现 UserDao 接口
*
*/
@Repository
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
System.out.println("UserDaoImpl 执行 add方法");
return a+b;
}
@Override
public String del(String id) {
System.out.println("UserDaoImpl 执行 del方法");
return id;
}
}
(3):使用Proxy类来对接口增强
package com.junjay.spring5.dao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class JdkProxy {
public static void main(String[] args) {
Class[] interfaces = { UserDao.class };
UserDaoImpl userDaoImpl = new UserDaoImpl();
// 创建接口实现类的代理对象
UserDao newProxyInstance = (UserDao) Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), interfaces,
new UserProxy(userDaoImpl));
// Proxy.newProxyInstance....这一步相当于是创建接口实现类以接口做接收
// UserDao userDaoImpl = new UserDaoImpl(); // 但改UserDao为动态代理的接口
int add = newProxyInstance.add(3, 7);
System.out.println("返回结果:"+add);
}
}
//创建代理对象代码
/**
* @author My 内部类(类中创建新类)
*/
class UserProxy implements InvocationHandler {
// 把创建的接口实现类,传递过来
private Object obj;
// 有参构造,参数为需要代理对象的实现类
public UserProxy(Object obj) {
this.obj = obj;
}
// 增强逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法之前
System.err.println(method.getName() + "方法执行前。。。。" + " 传递的参数=" + Arrays.toString(args));
// 被增强的方法执行
Object object = method.invoke(obj, args);
// 方法之后
System.err.println("方法执行后=" + obj);
return object;
}
}
测试代理对象:
一、JDK与CGLIB区别:
1.JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
2.CGLIB动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
javassist没有用过,说说cglib
在AOP中,Java原生自带的模式需要依赖于接口,
但是一些实现里面,是通过继承来实现的,压根就没有接口,或者只有很底层的接口。这种场景下,就没办法通过接口的这种动态代理方式来实现动作拦截。
所以只好依赖于cglib。
当然,最优雅的还是通过接口的方式,cglib只不过是无奈的选择罢了。另外,cglib是运行时动态修改字节码的。
AOP(术语)
(1):连接点
(2):切入点
(3):通知(增强)
(4):切面
AOP操作(准备)
1:在spring框架中一般都是基于AspectJ实现Aop操作
(1):什么是AspectJ
AspectJ不是spring组成部分,是独立的aop框架,一般把aspectj和spring一起使用,进行aop操作
2:基于aspectj实现aop操作
(1):基于xml配置文件实现
(2):基于注解方式实现
3:在项目工程里引入aop相关jar包
4:切入点表达式
(1):切入点表达式作用,确定哪个类里的哪个方法进行增强;
(2):语法结构:
execution(表达式)
pointcut=”execution( ...(..)) 访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
eg:对com.spring5.dao.BookDao接口类中的add进行增强
execution( com.spring5.dao.BookDao.add(..) )
eg:对com.spring5.dao.BookDao接口类中的所有方法进行增强
execution( com.spring5.dao.BookDao.(..) )
eg:对com.spring5.dao.BookDao接口中所有类中的所有方法进行增强
execution( com.spring5.dao..(..) )
AOP操作(aspectj注解)
1:创建一个类,在类里定义一个方法(被增强类)
package com.junjay.spring5.aopanno;
import org.springframework.stereotype.Component;
/**
* @author My
* 被增强类
*/
@Component
public class User {
public void add() {
// int i = 10/0;
System.out.println("user add ....");
}
}
2:创建增强类(编写增强逻辑)
package com.junjay.spring5.aopanno;
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.springframework.stereotype.Component;
/**
* @author My
* 增强User类
*/
@Component
@Aspect //生成代理对象
public class UserProxy {
/**
* 前置通知
*/
// @Before注解表示作为改方法的前置通知
@Before(value = "execution(* com.junjay.spring5.aopanno.User.add(..))")
public void beforeAdvice() {
System.err.println("@Before前置通知");
}
@AfterReturning(value = "execution(* com.junjay.spring5.aopanno.User.add(..))")
public void afterReturning() {
System.err.println("@AfterReturning后置通知");
}
/**
* @throws Throwable
* @Around 环绕通知,在执行add()方法之前、之后都执行
*/
@Around(value = "execution(* com.junjay.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.err.println("@Around环绕之前。。。");
// 执行被增强的方法
pjp.proceed();
System.err.println("环绕之后。。。");
}
@AfterThrowing(value = "execution(* com.junjay.spring5.aopanno.User.add(..))")
public void afterThrowing() {
System.err.println("@AfterThrowing异常通知");
}
@After(value = "execution(* com.junjay.spring5.aopanno.User.add(..))")
public void after() {
System.err.println("@After最终通知");
}
}
package com.junjay.spring5.aopanno;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* @author My
* 增强User类
*/
@Component
@Aspect //生成代理对象
public class UserProxy {
/**
* 前置通知
*/
// @Before注解表示作为改方法的前置通知
@Before(value = "execution(* com.junjay.spring5.aopanno.User.add(..))")
public void beforeAdvice() {
System.err.println("前置通知后,在执行原始方法。");
}
}
3:进行通知配置
(1):在spring配置文件中,开启注解扫描
(2):使用注解创建user,userproxy对象
(3):在增强类上面添加注解 @aspect
(4):在spring配置文件中开启生成代理对象
4:配置不同类型的通知
(1):在增强类里面,在作为通知方法上面添加通知类型
5:测试1,无异常通知
测试2,有异常通知
6:相同的切入点抽取
@Component
@Aspect //生成代理对象
public class UserProxy {
// 相同切入点抽取 Point-点 cut-切入
@Pointcut(value = "execution(* com.junjay.spring5.aopanno.User.add(..))")
public void pointdemo() {
}
/**
* 前置通知
*/
// @Before注解表示作为改方法的前置通知
// pointdemo 方法上已经有了execution切入点方法直接使用就可以
@Before(value = "pointdemo()")
public void beforeAdvice() {
System.err.println("@Before前置通知");
}
}
7: 有多个增强类对同一个方法进行增强,设置优先级
(1)在增强类上面添加注解@order (数值类型值) ,数字类型值越小优先级越高
package com.junjay.spring5.aopanno;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect //生成代理对象
@Order(2)
public class PerProxy {
/**
* 前置通知
*/
// @Before注解表示作为改方法的前置通知
@Before(value = "execution(* com.junjay.spring5.aopanno.User.add(..))")
public void beforeAdvice() {
System.err.println("PerProxy ----- @Before前置通知");
}
}
@Component
@Aspect //生成代理对象
@Order(1)
public class UserProxy {
// 相同切入点抽取 Point-点 cut-切入
@Pointcut(value = "execution(* com.junjay.spring5.aopanno.User.add(..))")
public void pointdemo() {
}
/**
* 前置通知
*/
// @Before注解表示作为改方法的前置通知
// pointdemo 方法上已经有了execution切入点方法直接使用就可以
@Before(value = "pointdemo()")
public void beforeAdvice() {
System.err.println("UserProxy ----- @Before前置通知");
}
}
AOP操作(AspectJ配置文件)
1:创建俩个类,增强类和被增强类,创建方法
package com.junjay.spring5.aopxml;
public class Book {
public void buy() {
System.out.println("book-------buy ");
}
}
package com.junjay.spring5.aopxml;
public class BookProxy {
public void before() {
System.out.println("BookProxy 前置通知!!!");
}
}
2:在spring配置文件中创建俩个类对象,spring配置文件中进行切入点通知配置配置
<?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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
">
<!-- 创建对象 -->
<bean id="book" class="com.junjay.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.junjay.spring5.aopxml.BookProxy"></bean>
<!-- aop增强配置 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* com.junjay.spring5.aopxml.Book.buy(..))" id="p"/>
<!-- 切面 把增强方法应用到被增强方法中 -->
<aop:aspect ref="bookProxy">
<!-- 增强作用在具体的方法上 method(before)设置增强类的方法 pointcut-ref增强的切入点id -->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
</beans>
3:测试
8:完全使用注解使用aspectj
(1):其他不变只是把配置文件转换成配置类
<context:component-scan
base-package="com.junjay.spring5"></context:component-scan>
<!-- 开启aspectj 生成代理对象,作用扫描有@aspectj注释的类,将其生成为代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
package com.junjay.spring5.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"com.junjay.spring5"})
// @EnableAspectJAutoProxy 开启aspectj 生成代理对象 默认为false
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}