版本:idea2020.1.1 jdk1.8 maven3.6.3
注:本文章摘自Java EE企业级应用开发教程,黑马程序员/编著,仅用于记录学习笔记,分享学习经历。
一、AOP术语
Aspect(切面):在实际应用中,切面通常是指封装的用于横向插入系统功能(如事务、日志等)的类。该类要被Spring容器识别为切面,需要在配置文件中通过<bean>元素指定。
Joinpoint(连接点):在程序执行过程中的某个阶段点,它实际上是对象的一个操作,例如方法的调用或异常的抛出。在SpringAOP中,连接点就是指方法的调用。
PointCut(切入点):是指切面与程序流程的交叉点,即那些需要处理的连接点。通常在程序中,切入点指的是类或方法名,如某个通知要应用到所有以add开头的方法中,那么所有满足这一规则的方法都是切入点。
Advice(通知、增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处索要执行的程序代码。可以将其理解为切面类中的方法,它是切面的具体实现。
Target Object(目标对象):是指所有被通知的对象,也被称为增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理的对象。
Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。
二、动态代理
1、JDK动态代理:对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP
UserDao_AOP1 ——> UserDaoImpl_AOP1 ——> Aspect_AOP1 ——> Proxy_AOP1 ——> AOP1_Test ——> 查看结果
public interface UserDao_AOP1 {
public void addUser();
public void delUser();
}
public class UserDaoImpl_AOP1 implements UserDao_AOP1 {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void delUser() {
System.out.println("删除用户");
}
}
public class Aspect_AOP1 {
public void Permissions(){
System.out.println("模拟检查权限");
}
public void log() {
System.out.println("模拟记录日志");
}
}
public class Proxy_AOP1 implements InvocationHandler {
//声明目标类接口
private UserDao_AOP1 userDao;
//创建代理方法
public Object createProxy(UserDao_AOP1 userDao) {
this.userDao = userDao;
//1.类加载器
ClassLoader classLoader = Proxy_AOP1.class.getClassLoader();
//2.被代理对象实现的所有接口
Class[] clazz = userDao.getClass().getInterfaces();
//3.使用代理类,进行增强,返回的是代理后的对象
return Proxy.newProxyInstance(classLoader,clazz,this);
}
/**
* 所有动态代理类的方法调用,都会交由invoke()方法去处理
* @param proxy 被代理后的对象
* @param method 将要被执行的方法信息(反射)
* @param args 执行方法时需要的参数
* @return
* @throws Throwable
*
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//声明切面
Aspect_AOP1 aspect = new Aspect_AOP1();
//前增强
aspect.Permissions();
//在目标类上调用方法,并传入参数
Object obj = method.invoke(userDao,args);
//后增强
aspect.log();
return obj;
}
}
Proxy_AOP1类实现了InvocationHandler接口,并实现了接口中的invoke方法,所有动态代理类所调用的方法都会交由该方法处理。在创建的代理方法createProxy()中使用了Proxy类的newProxyInstance()方法来创建代理对象。newProxyInstance()中包含三个参数,第一个是当前类的类加载器,第二个参数表示的是被代理对象实现的所有接口,第三个参数表示代理类Proxy_AOP1本身。在invoke()方法中,目标类方法执行的前后会分别执行切面类中的Permissions()方法和log()方法
public class AOP1_Test {
public static void main(String[] args) {
//创建代理对象
Proxy_AOP1 proxy = new Proxy_AOP1();
//创建目标对象
UserDao_AOP1 userDao = new UserDaoImpl_AOP1();
//从代理对象中获取增强后的目标对象
UserDao_AOP1 userDao1 = (UserDao_AOP1) proxy.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.delUser();
}
}
2、CGLIB代理:使用动态代理的对象必须实现一个或多个接口,如果要对没有实现接口的类进行代理,可以使用CGLIB代理。
CGLIB是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。在Spring的核心包中已经继承了CGLIB所需要的包。
UserDao_AOP2 ——> Proxy_AOP2 ——> AOP2_Test ——> 查看结果
public class UserDao_AOP2 {
public void addUser() {
System.out.println("添加用户");
}
public void delUser() {
System.out.println("删除用户");
}
}
public class Proxy_AOP2 implements MethodInterceptor {
//代理方法
public Object createProxy(Object target) {
//创建一个动态类对象
Enhancer enhancer = new Enhancer();
//确定需要增强的类,设置其父类
enhancer.setSuperclass(target.getClass());
//添加回调函数
enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
/**
*
* @param o 根据指定父类生成的代理对象
* @param method 拦截的方法
* @param objects 拦截方法的参数数组
* @param methodProxy 方法的代理对象,用于执行父类的方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//创建切面类对象
Aspect_AOP1 aspect = new Aspect_AOP1();
//前增强
aspect.Permissions();
//目标方法执行
Object obj = methodProxy.invokeSuper(o,objects);
//后增强
aspect.log();
return obj;
}
}
首先创建了一个动态类对象Enhancer,它是CGLIB的核心类;然后调用了Enhancer类的setSuperclass()方法来确定目标对象;接下来调用了setCallback()方法添加回调函数,其中的this代表的就是代理类Proxy_AOP2本身;最后通过return语句将创建的代理类对象返回。Intercept()方法会在程序执行目标方法时被调用,方法运行时将会执行切面类中的增强方法。
public class AOP2_Test {
public static void main(String[] args) {
//创建代理对象
Proxy_AOP2 proxy = new Proxy_AOP2();
//创建目标对象
UserDao_AOP2 userDao = new UserDao_AOP2();
//获取增强后的目标对象
UserDao_AOP2 userDao1 = (UserDao_AOP2) proxy.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.delUser();
}
}
三、基于代理类AOP实现
实际上Spring的AOP代理默认就是使用JDK动态代理的方式来实现的。在Spring中,使用ProxyFactoryBean是创建AOP代理的最基本方式。
1、通知类型
org.aopalliance.intercept.MethodInterceptor(环绕通知) | 在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。 |
org.springframework.aop.MethodBeforeAdvice(前置通知) | 在目标方法执行前实施增强,可以应用于权限管理等功能。 |
org.springframework.aop.AfterReturningAdvice(后置通知) | 在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。 |
org.springframework.aop.ThrowsAdvice(异常通知) | 在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。 |
org.springframework.aop.IntroductionInterceptor(引介通知) | 在目标类中添加一些新的方法和属性,可以应用于修改老版本程序(增强类)。 |
2、ProxyFactoryBean:是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactory负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean是创建AOP代理的基本方式。
属性名 | 描述 |
target | 代理的目标对象 |
proxyInterfaces | 代理要实现的接口,如果是多个接口使用 <list> <value></value> ...... </list> |
proxyTargetClass | 是否对类代理而不是接口,设置为true时使用CGLIB代理 |
interceptorNames | 需要织入目标的Advice |
singleton | 返回的代理是否为单实例,默认为true(返回单实例) |
optimize | 当设置为true时强制使用CGLIB |
导入maven依赖 ——> Aspect_AOP3 ——> UserDao_AOP1 ——> UserDaoImpl_AOP1 ——> beans_AOP3.xml ——> ProxyFactoryBeanTest ——> 查看结果
<!--spring aop 依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<!--spring aop 依赖-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
public class Aspect_AOP3 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Permissions();
//执行目标方法
Object obj = methodInvocation.proceed();
log();
return obj;
}
public void Permissions() {
System.out.println("模拟检查权限");
}
public void log() {
System.out.println("模拟日志记录");
}
}
public interface UserDao_AOP1 {
public void addUser();
public void delUser();
}
public class UserDaoImpl_AOP1 implements UserDao_AOP1 {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void delUser() {
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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 1.目标类 -->
<bean id="userDao" class="com.jc.dao.impl.UserDaoImpl_AOP1"/>
<!-- 2.切面类 -->
<bean id="Aspect_AOP3" class="com.jc.aspect.Aspect_AOP3"/>
<!-- 3.使用Spring代理工厂定义一个名称为userDaoProxy的代理对象-->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 3.1 指定代理实现的接口 -->
<property name="proxyInterfaces" value="com.jc.dao.UserDao_AOP1"/>
<!-- 3.2 指定目标对象 -->
<property name="target" ref="userDao"/>
<!-- 3.3 指定切面,植入环绕通知 -->
<property name="interceptorNames" value="Aspect_AOP3"/>
<!-- 3.4 指定代理方式,
ture:使用cglib,
false(默认):使用jdk动态代理-->
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
public class ProxyFactoryBeanTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans_AOP3.xml");
//从spring容器获得内容
UserDao_AOP1 userDao = (UserDao_AOP1) applicationContext.getBean("userDaoProxy");
//执行方法
userDao.addUser();
userDao.delUser();
}
}
四、AspectJ开发
1、基于XML文件来定义切面、切入点及通知,所有切面、切入点和通知都必须定义在<aop:config>元素内。
<aop:pointcut>、<aop:advisor>、<aop:aspect>在配置时,必须按照此顺序来定义
<?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.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 定义切面Bean -->
<bean id="aspect" class="com.jc.aspect.Aspect"/>
<aop:config>
<!-- 1.配置切面-->
<aop:aspect id="aspect" ref="aspect">
<!-- 2.配置切入点 -->
<aop:pointcut id="pointCut" expression="execution(* com.jc.dao.*.*(..))"/>
<!-- 3.配置通知 -->
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pointCut"/>
<!-- 后置通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="returnVal"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pointCut"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="e"/>
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
配置切面:配置切面使用的是<aop:aspect>,该元素会将一个已定义好的SpringBean转换成切面Bean,所以要在配置文件中先定义一个普通的SpringBean。
配置切入点:配置切入点使用的是<aop:pointcut>,当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可以被多个切面所共享,当<aop:pointcut>作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。
常用切入点表达式:execution(* com.jc.dao.*.*(..)),该表达式的意思是匹配com.jc.dao包下任意类的任意方法任意参数执行。execution()是表达式主体,第一个*代表返回值类型,com.jc.dao表示拦截的包名,第二个*表示的是类名,第三个*表示的是方法名(..)表示方法的参数。
SpringAOP切入点表达式基本格式:
execution(modifiers-pattern? ret-type-pattern? declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:表示定义的目标方法的访问修饰符,如public、private等。
ret-type-pattern:表示定义的目标方法的返回值类型,如void、String等。
declaring-type-pattern:表示定义的目标方法的类路径,如com.jc.dao。
name-pattern:表示具体需要被代理的目标方法,如add()方法。
param-pattern:表示需要被代理的目标方法包含的参数。
throws-pattern:表示需要被代理的目标方法抛出的异常类型。
其中带有?的,表示可配置项,其他属于必须配置项。
配置通知:
属性名称 | 描述 |
pointcut | 该属性用于指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入该通知 |
pointcut-ref | 该属性指定一个已经存在的切入点名称,如配置代码中的pointCut。通常pointcut与pointcut-ref只需要一个 |
method | 该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理 |
throwing | 该属性只对<after-throwing>元素有效,它用于指定一个形参名,异常通知方法可以通过该形参访问目标方法所抛出的异常 |
returning | 该属性只对<after-returning>元素有效,它用于指定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值 |
导入maven依赖 ——> UserDao_AOP2 ——> AspectJ1 ——> beans_Asp1.xml ——> AspectJ1_Test ——> 查看测试结果
<!--提供对AspectJ的支持,以便可以方便的将面向方面的功能集成进IDE中,比如Eclipse AJDT。外部依赖。-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<!--aspectjweaver是aspectj的织入包(必须)-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
<!--aspectj的runtime包(必须)-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.1</version>
</dependency>
public class UserDao_AOP2 {
public void addUser() {
System.out.println("添加用户");
}
public void delUser() {
System.out.println("删除用户");
}
}
public class AspectJ1 {
//前置通知
public void befory(JoinPoint joinPoint) {
System.out.println("前置通知:模拟执行权限检查。。。");
System.out.println("目标类是:"+joinPoint.getTarget());
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
//后置通知
public void after(JoinPoint joinPoint) {
System.out.println("后置通知:模拟记录日志。。。");
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
/**
* 环绕通知
* @param proceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法
* 1.必须是Object类型的返回值
* 2.必须接受一个参数,类型为ProceedingJoinPoint
* 3.必须throws Throwable
* @return
* @throws Throwable
*/
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务。。。");
//执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务。。。");
return obj;
}
//异常通知
public void afterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:"+"出错了 "+ e.getMessage());
}
//最终通知
public void afterm() {
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.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1.目标类 -->
<bean id="userDao" class="com.jc.dao.UserDao_AOP2"/>
<!-- 2.切面 -->
<bean id="aspect" class="com.jc.aspect.AspectJ1"/>
<!-- 3.aop编程 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="aspect">
<!-- 3.1 配置切入点,通知最后增强那些方法 -->
<aop:pointcut id="pointCut" expression="execution(* com.jc.dao.*.*(..))"/>
<!-- 3.2 关联通知Advice和切入点pointCut -->
<!-- 3.2.1 前置通知 -->
<aop:before method="befory" pointcut-ref="pointCut"/>
<!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值
returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
<aop:after-returning method="after" pointcut-ref="pointCut" returning="returnVal"/>
<!-- 3.2.3 环绕通知 -->
<aop:around method="around" pointcut-ref="pointCut"/>
<!-- 3.2.4 抛出通知:用于处理程序发生异常
如程序没有异常,将不会执行增强
throwing属性:用于设置通知第二个参数的名称,类型Throwable-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="e"/>
<!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行-->
<aop:after method="afterm" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
public class AspectJ1_Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans_Asp1.xml");
UserDao_AOP2 userDao = (UserDao_AOP2) applicationContext.getBean("userDao");
userDao.addUser();
userDao.delUser();
}
}
2、基于注解的声明式AspectJ
注解名称 | 描述 |
@Aspect | 用于定义一个切面 |
@Pointcut | 用于定义切入点表达式。在使用时还需定义一个包含名字和惹你参数的方法签名来表示切入点名称。实际上,这个方法签名就是一个返回值为void,且方法体为空的普通的方法。 |
@Before | 用于定义前置通知,相当于BeforeAdvice。在使用时,通常需要制定一个value属性值,该属性值用于指定一个切入点表达式(可以是已有的切入点,也可以直接定义切入点表达式) |
@AfterReturning | 用于定义后置通知,相当于AfterReturningAdvice。在使用时可以指定pointcut/value和returning属性,其中pointcut/value两个属性的作用一样,都用于指定切入点表达式。returning属性值用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值。 |
@Around | 用于定义环绕通知,相当于MethodInterceptor。在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。 |
@AfterThrowing | 用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice。在使用时可指定pointcut/value和throwing属性。其中pointcut/value用于指定切入点表达式,而throwing属性值用于指定一个形参名来表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。 |
@After | 用于定义最终final通知,不管是否异常,该通知都会执行。使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。 |
@DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor。 |
UserDao ——> UserDaoImpl ——> UserController ——> AopLogging ——> applicationContext.xml ——> testXml ——> 查看测试结果
public interface UserDao {
public void say();
}
public class UserDaoImpl implements UserDao {
@Override
public void say() {
System.out.println("userDao say hello World!");
}
}
public class UserController {
public void addUser() {
System.out.println("添加方法");
}
}
@Aspect //标注这是一个切面类
public class AopLogging {
//配置公共切点
@Pointcut("execution(* AspectJ.*.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
// 获取方法名称
String name = joinPoint.getSignature().getName();
// 获取方法参数
Object[] args = joinPoint.getArgs();
}
@AfterReturning(pointcut="pointcut()")
public void afterReturning(JoinPoint joinPoint) {
// 获取方法名称
String name = joinPoint.getSignature().getName();
// 打印方法执行结果
System.out.println(name);
}
@AfterThrowing(pointcut="pointcut()",throwing="exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
// 获取方法名称
String name = joinPoint.getSignature().getName();
// 打印异常信息
System.out.println(name + "运行异常信息:" + exception);
}
@After("pointcut()")
public void after(JoinPoint joinPoint) {
System.out.println("最终通知");
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint){
//这里操作相当于前置通知
System.out.println("环绕==前置通知");
Object obj = null;
try {
obj = joinPoint.proceed();
//这里操作相当于后置通知
System.out.println("环绕==后置通知");
} catch (Throwable e) {
//这里操作相当于异常通知
System.out.println("环绕==异常通知");
e.printStackTrace();
}
//这里操作相当于最终通知
System.out.println("环绕==最终通知");
return obj;
}
}
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描所有包
<context:component-scan base-package="com.dao"/>-->
<!-- 将指定类配置给 Spring,让Spring创建其对象的实例 -->
<bean id="userDao" class="com.jc.dao.impl.UserDaoImpl" />
<bean id="userService" class="com.jc.service.impl.UserServiceImpl">
<!-- 将id为userDao的Bean实例注入到userService实例中 -->
<property name="userDao" ref="userDao"/>
</bean>
</beans>
public class testXml {
public static void main(String[]args){
// 创建容器
ApplicationContext context=new ClassPathXmlApplicationContext(
"AspectJ.xml");
// 从容器中获取bean
UserController userController = (UserController) context.getBean("userController");
// 调用方法
userController.addUser();
}
}