SpringAOP(四)

本文介绍了 Spring AOP 相关知识,包括专业术语如切面、连接点等。详细阐述了动态代理,有 JDK 动态代理和 CGLIB 代理。还介绍了 Spring 的通知类型、ProxyFactoryBean 创建 AOP 代理的方式,以及 AspectJ 开发,包含基于 XML 和注解的声明式 AspectJ。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.专业术语

  • Aspect(切面):指封装的用于横向插入系统功能(如事务、日志等的类),该类要被spring容器识别为切面,需要在配置文件中通过<bean>元素指定
  • Joinpoint(连接点):指对象的一个操作,例如方法的调用或者异常的抛出,在SpringAOP中,连接点就是指方法的调用.
  • Poincut(切入点):指的是类或方法名,如某个通知要应用到所有以add开头的方法中,那么所有满足这一规则的方法就是切入点
  • Advice(通知/增强处理):在定义好的切入点处要执行的程序代码,可以将其理解为切面类中的方法,它是切面类中的具体体现.
  • Target Object(目标对象):是指所有被通知的对象.
  • Proxy(代理):将通知应用到目标对象之后,被动态创建的对象
  • Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程

2.动态代理

2.1JDK动态代理

JDK动态代理是通过java.long.reflect.Proxy类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象

案例:

(1)UserDao

public interface UserDao{
public void addUser();
public void deleteUser();
}

(2)UserDaoImpl

//目标类
public class UserDaoImpl implements UserDao{
public void addUser(){
System.out.println("添加用户");
}
public void deleteUser(){
System.out.println("删除用户");
}
}

(3)MyAspect

//切面类:可以存在多个通知Advice(即增强的方法)
public class MyAspect{
public void check_Permissions(){
System.out.println("模拟检查权限");
}
public void log(){
System.out.println("模拟记录日志");
}
}

(4)创建代理类JdkProxy

//jdk代理类
public class JdkProxy implements InvocationHandler{
//声明目标类接口
private UserDao userDao;
//创建代理方法
public Object createProxy(UserDao userDao){
this.userDao=userDao;
//1.类加载器
ClassLoader classLoader=JdkProxy.class.getClassLoader();
//2.被代理对象实现的所有接口
Class[] clazz=userDao.getClass().getInterfaces();
//3.使用代理类,进行增强,返回的是代理后的对象
return Proxy.newProxyInstance(classLoader,clazz,this);
}
/*

所有动态代理类的方法调用都交给invoke()方法处理
proxy被代理后的对象
method将要被执行的方法信息(反射)
args执行方法时需要的参数
*/
@Override
public Object invoke(Object proxy,Method method,Object[]args)throws Throwable{
//声明切面
MyAspect myAspect=new MyAspect();
//前增强
myAspect.check_Permissions();
//在目标类上调用方法,并传入参数
Object obj=method.invoke(userDao,args);
//后增强
myAspect.log();
return obj;
}
}

(5)JdkTest

public class JdkTest{
public static void main (String []args){
//创建代理对象
JdkProxy jdkProxy=new JdkProxy();
//创建代理对象
JdkProxy jdkProxy=new JdkProxy();
//创建目标对象
UserDao userDao=new UserDaoImpl();
//从代理对象中获取增强后的目标对象
UserDao userDao1=(UserDao)jdkProxy.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.deleteUser();
}
}

 

2.2CGLIB代理

如果要对没有实现接口的类实现代理,那么可以使用CGLIB代理

(1)UserDao

​
//目标类
public class UserDao {
public void addUser(){
System.out.println("添加用户");
}
public void deleteUser(){
System.out.println("删除用户");
}
}

​

(2)创建代理类CglibProxy

//代理类
public class CglibProxy implements MethodInterceptor{
//代理方法
public Object createProxy(Object target){
//创建一个动态类对象
Enhancer enhancer=new Enhancer();
//确定需要增强的类,设置其父类
enhancer.setSuperclass(target.getClass());
//添加回调函数
enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
/*
proxy CGlib 根据指定父类生成的代理对象
method 拦截的方法
args拦截方法的参数数组
methodProxy 方法的代理对象,用于执行父类的方法
*/
@Override
public Object intercept(Object proxy,Method method,Object[]args,MethodProxy methodProxy)throws Throwable{
//创建切面类对象
MyAspect myAspect=new MyAspect();
//前增强
myAspect.check_Permissions();
//目标方法执行
Object obj=methodProxy.invokeSuper(proxy,args);
//后增强
myAspect.log();
return obj;
}
}

(3)CglibTest

//测试类
public class CglibTest{
public static void main (String []args){
//创建代理对象
CglibProxy cglibProxy=new CglibProxy();
//创建目标对象
UserDao userDao=new UserDao();
//获取增强后的目标对象
UserDao userDao1=(UserDao)cglibProxy.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.deleteUser();
}
}

3.Spring的通知类型

Spring的通知类型
MethodInterceptor(环绕通知)在目标方法执行前后实施增强,可以应用于日志、事务管理等功能
MethodBeforeAdvice(前置通知)在目标方法执行前实施增强,可以应用于权限管理等功能
AfterReturingAdvice(后置通知)在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能
ThrowsAdvice(异常通知)在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能.
IntroductionInterceptor(引介通知)增强类(在目标类中添加一些新的方法和属性,可以应用于修改老版本程序)

 

 

 

 

 

 

  •  

 

 

4.ProxyFactoryBean

ProxyFactoryBean负责为其它Bean创建代理实例,在Spring中使用ProxyFactoryBean是创建AOP代理的基本方式

ProxyFactoryBean的常用属性
target代理的目标对象
proxyInterfaces

代理要实现的接口,如果是多个接口,可以使用以下格式赋值

<list>

<value></value>

</list>

proxyTargetClass是否对类代理而不是接口,设置为true时,使用CGLIB代理
interceptorNames需要织入目标的Advice
singleton返回的代理是否为单实例,默认为true(即返回单实例)
optimize当设置为true时,强制使用CGLIB

 

 

 

 

 

 

 

 

 

接下来通过一个典型的环绕通知案例,来掩饰spring使用ProxyFactoryBean创建AOP代理的过程

(1)factorybean包,在该包中创建切面类MyAspect

//切面类
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation mi) throws Throwable{
check_Permissions();
//执行目标方法
Object obj=mi.proceed();
log();
return obj;
}
public void check_Permissions(){
System.out.println("模拟检查权限");
}
public void log(){
System.out.println("模拟记录日志");
}
}

(2)applicationContext.xml中指定代理对象

<!--1 目标类-->
<bean id="userDao" class="com.itcast.jdk.UserDaoImpl"/>
<!--2切面类-->
<bean id="userDao" class="com.itcast.factorybean.MyAspect"/>
<!--3使用spring代理工厂定义一个名称为userDaoProxy的代理对象-->
<bean id="userDaoProxy" class="org.springframework.aop.framework.proxyFactoryBean"/>
<!--3.1指定代理实现的接口-->
<property name="proxyInterfaces" value="com.itcast.jdk.UserDao"/>
<!--3.2 指定目标对象-->
<proxy name="target" ref="userDao"/>
<!--3.3 指定切面,植入环绕通知-->
<property name="interceptorNames" value="myAspect"/>
<!--3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理-->
<property name="proxyTargetClass" value="true"/>
</bean>

(3)ProxyFactoryBeanTest

//测试类
public class ProxyFactoryBeanTest{
public static void main(String[]args){
String xmlpath="com/itcast/factorybean/applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlpath);
//从spring容器获得内容
UserDao userDao=(UserDao) applicationContext.getBean("userDaoProxy");
//执行方法
userDao.addUser();
userDao.deleteUser();
}
}

5.AspectJ开发

5.1基于XML的声明式AspectJ

1.配置切面

在spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的StringBean转换成切面Bean,所以要在配置文件中定义一个普通的SpringBean.定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean

配置<aop:aspect>元素时,通常会指定id和ref两个属性

<aop:aspect>元素的属性及其描述
属性名称描述
id用于定义该切面的唯一标识名称
ref用于引用普通的SpringBean

 

 

 

 

2.配置切入点

当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享,当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效.

<aop:pointcut>配置切入点
属性名称描述
id用于指定切入点的唯一标识名称
expression用于指定切入点关联的切入点表达式

 

 

 

 

切入点表达式的基本格式如下:

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.itcast.userDaoImpl
name-pattern表示具体需要被代理的目标方法,如add()
param-pattern表示需要被代理的目标方法包含的参数
throws-pattern表示需要被代理的目标方法抛出的异常类型
可配置项modifiers-pattern、declaring-type-pattern、throws-pattern

 

 

 

 

 

 

 

 

 

 

 

execution(* com.itcast.jdk.*.*(..))
上述表达式解释
execution表达式的主体
第1个*表示返回类型,使用*代表所有类型
com.itcast.jdk表示需要拦截的包名
第2个*表示的是类名,使用*代表所有的类
第3个*表示的是方法名,使用*表示的是所有方法
(..)表示方法的参数,其中..表示任意参数
注意:第一个*与包名之间有一个空格

 

 

 

 

 

 

 

 

3.配置通知

<aop:aspect>的子元素配置了5中常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性

<aop:aspect>通知的常用属性
pointcut该属性用于指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入该通知
pointcut-ref

该属性指定一个已经存在的切入点名称,通常pointcut

和pointcut-ref两个属性只需要使用其中之一

method该属性制定一个方法名,指定将切面Bean中的该方法转换为增强处理
throwing该属性只对<after-throwing>元素有效,它用于指定一个形参名,异常通知方法可以通过该形参访问目标方法所抛出的异常
returning该属性只对<after-returning>元素有效,它用于指定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值

 

 

 

 

 

 

 

 

 

通过一个案例演示如何在Spring中通过基于xml的声明式AspectJ

(1)MyAspect切面类

//切面类,在此类中编写通知
public class MyAspect{
//前置通知
public void myBefore(JoinPoint joinpoint){
System.out.println("前置通知,模拟执行权限检查...");
System.out.println("目标类是:"+joinpoint.getTarget());
System.out.println("被植入增强处理的目标方法为:"+joinpoint.getSignature().getName());
}
//后置通知
public void myAfterReturning(JoinPoint joinpoint){
System.out.println("后置通知,模拟记录日志...");
System.out.println("目标类是:"+joinpoint.getTarget());
System.out.println("被植入增强处理的目标方法为:"+joinpoint.getSignature().getName());
}
/*环绕通知
proceedingJoinPoint是JoinPoint子接口,表示可以执行目标方法
1.必须是Object类型的返回值
2.必须接受一个参数,类型为proceedingJoinPoint
3.必须throws Throwable
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
//执行当前目标方法
Object obj=proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinpoint,Throwable e){
System.out.println("异常通知:"+"出错了"+e.getMessage());
}
//最终通知
public void myAfter(){
System.out.println("最终通知:模拟方法结束后的释放资源");
}
}

(2)applicationContext.xml

<!--1.目标类-->
<bean id="userDao" class="com.itcast.jdk.UserDaoImpl"/>
<!--2.切面-->
<bean id="myAspect" class="com.itcast.aspectj.xml.MyAspect"/>
<!--3.aop编程-->
<aop:config>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--3.1配置切入点,通知最后增强哪些方法-->
<aop:pointcut expression="execution(* com.itcast.jdk.*.*(..))" id="myPointCut">
<!--3.2关联通知Advice和切入点pointcut-->
<!--3.2.1前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!--3.2.2后置通知,在方法返回之后执行,就可以获得返回值
returning属性:用于设置后置通知的第二个参数的名称,类型是Object-->
<aop:after-returning method="myAfterReturning"
pointcut-ref="myPointCut" returning="returnVal"/>
<!--3.2.3环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!--3.2.4抛出通知:用于处理程序发生异常-->
<!--如果程序没有异常,将不会执行增强-->
<!--throwing属性:用于设置通知第二个参数的名称,类型Throwable-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> 
<!--3.2.5最终通知无论程序发生任何事情,都将执行-->
<aop:after method="myAfter" pointcut-ref="myPointCut">
</aop:aspect>
</aop:config>

(3)测试类TestXmlAspectj

//测试类
public class TestXmlAspectj{
public static void main(String[]args){
String xmlpath="com/itcast/aspectj/xml/applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlpath);
//1.从spring容器获得内容
UserDao userDao=(UserDao)applicationContext.getBean("userDao");
//2.执行方法
userDao.addUser();
}
}

5.2基于注解的声明式AspectJ

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

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值