SpringAOP编程的三种方式

1.传统AOP编程

传统SpringAOP的Advice 必须实现对应的接口!

  1. 前置通知 org.springframework.aop.MethodBeforeAdvice
    在目标方法执行前实施增强
  2. 后置通知 org.springframework.aop.AfterReturningAdvice
    在目标方法执行后实施增强
  3. 环绕通知 org.aopalliance.intercept.MethodInterceptor
    在目标方法执行前后实施增强
  4. 异常抛出通知 org.springframework.aop.ThrowsAdvice
    在方法抛出异常后实施增强
  5. 引介通知 org.springframework.aop.IntroductionInterceptor
    在目标类中添加一些新的方法和属性

1.1 测试:通过AOP,记录Controller层方法的运行时间

流程:

步骤一.编写advice通知,根据需要实现对应的接口

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class RuntimeAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //方法调用之前记录时间
        long beginTime = System.currentTimeMillis();

        //目标对象原来的方法的调用,返回目标对象方法的返回值。
        Object invoke = methodInvocation.proceed();//类似于invoke

        //方法调用之后记录时间
        long endTime = System.currentTimeMillis();

        //计算运行时间(毫秒)
        long runTime=endTime-beginTime;

        System.out.println(methodInvocation.getMethod().getName() + "方法执行时间为:" + runTime + "毫秒");

        return invoke;
    }
}

步骤二.将通知注册到Spring容器中
applicationContext.xml

<bean id="runtimeAdvice" class="demo.advice.RuntimeAdvice"></bean>

步骤三.配置切入点和切面

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       					   http://www.springframework.org/schema/beans/spring-beans.xsd
						   http://www.springframework.org/schema/context 
						   http://www.springframework.org/schema/context/spring-context.xsd
						   http://www.springframework.org/schema/aop 
                           http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="runtimeAdvice" class="demo.advice.RuntimeAdvice"></bean>

<aop:config>
    <aop:pointcut expression="execution(* demo.controller..*.*(..))" id="myPointcut"/>
    <aop:advisor advice-ref="runtimeAdvice" pointcut-ref="myPointcut"/>
</aop:config>

测试:

@Controller
public class UserController {

	@RequestMapping(value = "test")
    public String test(HttpServletRequest request) {
        return "test";
    }
    
}

问题:
启动tomcat服务器后,我们访问 localhost:8080/test,我们发现并没有走到RuntimeAdvice中的环绕方法中,这是为什么呢?

原因:
Spring和SpringMVC两个容器,它们的配置文件分别为applicationContext.xmlxx-servlet.xml。Spring是根容器,SpringMVC是其子容器。SpringMVC容器可以访问Spring容器中的Bean,Spring容器不能访问SpringMVC容器的Bean
SPRING容器与SPRINGMVC容器的区别与联系

解决方式:
在SpringMVC的配置文件中定义

<bean id="runtimeAdvice" class="demo.advice.RuntimeAdvice"></bean>

<aop:config>
    <aop:pointcut expression="execution(* demo.controller..*.*(..))" id="myPointcut"/>
    <aop:advisor advice-ref="runtimeAdvice" pointcut-ref="myPointcut"/>
</aop:config>

2.AspectJ 切面编程(xml方式)

spring2.0 支持AspectJ 语法 ,简化AOP开发。

开发方法还是三步:

  1. 确定目标对象(bean)
  2. 编写通知,对目标对象增强(advice)
  3. 配置切入点(pointcut)、切面(aspect)

2.1 AspectJ 提供Advice类型

普通的pojo即可。(不需要实现接口)

  • Before 前置通知,相当于BeforeAdvice
  • AfterReturning 后置通知,相当于AfterReturningAdvice
  • Around 环绕通知,相当于MethodInterceptor
  • AfterThrowing抛出通知,相当于ThrowAdvice
  • After 最终final通知,不管是否异常,该通知都会执行
  • DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)

2.2 分析各种通知应用

1.Before前置通知
案例应用: 实现权限控制 (即:权限不足的时候,抛出异常)、 记录方法调用信息日志

第一步:配置MyAspect类(切面),配置before方法(通知)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {	
	//前置通知的:方法运行之前增强
	//应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志 
	//参数:org.aspectj.lang.JoinPoint
	//参数:连接点对象(方法的包装对象:方法,参数,目标对象)
	public void before(JoinPoint joinPoint){
		//分析:抛出异常拦截的
		//当前登录用户
		String loginName = "Rose";
		System.out.println("方法名称:"+joinPoint.getSignature().getName());
		System.out.println("目标对象:"+joinPoint.getTarget().getClass().getName());
		System.out.println("代理对象:"+joinPoint.getThis().getClass().getName());
		//判断当前用户有没有执行方法权限
		if(joinPoint.getSignature().getName().equals("save")){
			if(!loginName.equals("admin")){
				//只有超级管理员admin有权限,其他人不能执行某个方法,比如查询方法
				throw new RuntimeException("您没有权限执行方法:"+joinPoint.getSignature().getName()+",类型为:"+joinPoint.getTarget().getClass().getName());
			}
		}
		
	}
}

第二步:Spring容器中配置,配置applicationContext-aspectJ.xml(通知)

<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.service.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.service.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.aspect.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 前置通知 -->
		<aop:before method="before" pointcut-ref="myPointcut" />
	</aop:aspect>

</aop:config>

2.AfterReturing 后置通知
应用场景:与业务相关的,如网上营业厅查询余额后,自动下发短信功能。

分析: 后置通知可以获取到目标方法返回值,如果想对返回值进行操作,使用后置通知(但不能修改目标方法返回 )

第一步:配置MyAspect类(切面),配置afterReturing方法(通知)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {	
	//应用场景:与业务相关的,如网上营业厅查询余额后,自动下发短信。
	//后置通知:会在目标方法执行之后调用通知方法增强。
	//参数1:连接点对象(方法的包装对象:方法,参数,目标对象)
	//参数2:目标方法执行后的返回值,类型是object,“参数名”随便,但也不能太随便,一会要配置
	public void afterReturing(JoinPoint joinPoint,Object returnVal){
		//下发短信:调用运行商的接口,短信猫。。。
		System.out.println("-++++++++-后置通知-当前下发短信的方法"+"-尊敬的用户,您调用的方法返回余额为:"+returnVal);
		 
	}
}

第二步:Spring容器中配置,配置applicationContext-aspectJ.xml

<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.service.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.service.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.aspect.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 后置通知 
			returning:配置方法中的参数名字,与通知方法的第二个参数的名字,名字必须对应。
			在运行的时候,spring会自动将返回值传入该参数中。
		-->
		<aop:after-returning method="afterReturing" returning="returnVal" pointcut-ref="myPointcut"/>
	</aop:aspect>

</aop:config>

3.Around 环绕通知
应用场景:日志、缓存、权限、性能监控、事务管理

第一步:配置MyAspect类(切面),配置around方法(通知)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {	
	//应用场景:日志、缓存、权限、性能监控、事务管理
	//环绕通知:在目标对象方法的执行前+后,可以增强
	//参数:可以执行的连接点对象ProceedingJoinPoint(方法),特点是调用proceed()方法可以随时随地执行目标对象的方法(相当于目标对象的方法执行了)
	//必须抛出一个Throwable
	public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
		//目标:事务的控制:
		//开启事务:
		System.out.println("-----开启了事务。。。。。。。。。");
		//执行了目标对象的方法
		Object resultObject = proceedingJoinPoint.proceed();
		//结束事务
		System.out.println("-----提交了事务。。。。。。。。。");
		return resultObject;//目标对象执行的结果
	}
}

第二步:Spring容器中配置,配置applicationContext-aspectJ.xml

<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.service.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.service.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.aspect.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 环绕通知 -->
		<aop:around method="around" pointcut-ref="myPointcut"/>
	</aop:aspect>

</aop:config>

4.AfterThrowing 抛出通知
作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
应用场景:处理异常(一般不可预知),记录日志

第一步:配置MyAspect类(切面),配置aterThrowing方法(通知)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {	
	//作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
	//只有目标对象方法抛出异常,通知才会执行
	//参数1:静态连接点(方法对象)
	//参数2:目标方法抛出的异常,参数名随便,但也不能太随便
	public void afterThrowing(JoinPoint joinPoint,Throwable ex){
		//一旦发生异常,发送邮件或者短信给管理员
		System.out.println("++管理员您好,"+joinPoint.getTarget().getClass().getName()+"的方法:"
		+joinPoint.getSignature().getName()+"发生了异常,异常为:"+ex.getMessage());
	}
}

第二步:Spring容器中配置,配置applicationContext-aspectJ.xml

<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.service.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.service.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.aspect.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 抛出通知
			throwing:通知中的方法的第二个参数,异常类型的参数的名字,在运行的时候,spring会自动将异常传入该参数中。-->
		<aop:after-throwing method="aterThrowing" throwing="ex" pointcut-ref="myPointcut"/>		
       </aop:aspect>

</aop:config>

5.After 最终通知
作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)
应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )

第一步:配置MyAspect类(切面),配置after方法(通知)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {	
	//应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
	//最终通知:不管是否有异常都会执行
	public void after(JoinPoint joinPoint){
		//释放数据库连接
		System.out.println("数据库的connection被释放了。。。。。,执行的方法是:"+joinPoint.getSignature().getName());
	}
}

第二步:Spring容器中配置,配置applicationContext-aspectJ.xml

<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.service.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.service.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.aspect.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 最终通知 -->
		<aop:after method="after" pointcut-ref="myPointcut"/>
           <!--  以上代码也可以写成:pointcut切入点表达式:只能给一个通知方法来用,相当于省略了<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<aop:after method="after" pointcut="bean(*Service)"/>-->
	</aop:aspect>

</aop:config>

五种通知小结:
只要掌握Around(环绕通知)通知类型,就可实现其他四种通知效果。

因为你可以在环绕通知的方法中编写如下代码:

try {
	//前置通知
	Object result = proceedingJoinPoint.proceed();	// 环绕通知
	//后置通知
}catch(Exception){
	//抛出通知
}finally{
	//最终通知
}

2.3 各种Advice方法可接收的参数和返回值小结(参考)
通知类型输入参数(可选)返回值类型其他
Before前置通知JoinPoint(静态连接点信息)void
AfterReturning后置通知JoinPoint, Objectvoid
Around环绕通知ProceedingJoinPoint(可执行的连接点信息)Objectthrows Throwable
AfterThrowing抛出通知JJoinPoint, Throwablevoid
After最终通知JoinPoint(静态连接点信息)void

3.@AspectJ 注解配置切面编程

3.1 编写目标对象 (bean)、spring容器、测试类

3.1.1 编写目标对象(bean)

1.创建接口CustomerService.java

//接口
public interface CustomerService {
	//保存
	public void save();
	
	//查询
	public int find();

}

2.创建接口的实现类,CustomerServiceImpl

//实现类
/**
 * @Service("customerService")
 * 相当于spring容器中定义:
 * <bean id="customerService" class="cn.itcast.spring.a_aspectj.CustomerServiceImpl">
 */
@Service("customerService")
public class CustomerServiceImpl implements CustomerService{

	public void save() {
		System.out.println("客户保存了。。。。。");
		
	}

	public int find() {
		System.out.println("客户查询数量了。。。。。");
		return 100;
	}

}

3.创建类ProductService.java,不需要实现接口

//没有接口的类
/**
 * @Service("productService")
 * 相当于spring容器中定义:
 * <bean id="productService" class="cn.itcast.spring.a_aspectj.ProductService">
 */
@Service("productService")
public class ProductService {
	public void save() {
		System.out.println("商品保存了。。。。。");
		
	}

	public int find() {
		System.out.println("商品查询数量了。。。。。");
		return 99;
	}
}

3.1.2 配置applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

使用bean注解的扫描(自动开启注解功能)

<!-- 1。确定目标 -->
<!-- 扫描bean组件 -->
<context:component-scan base-package="cn.itcast.spring"/>

3.1.3 测试代码SpringTest.java

//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
	//注入要测试bean
	@Autowired
	private CustomerService customerService;
	@Autowired
	private ProductService productService;
	
	//测试
	@Test
	public void test(){
		//基于接口
		customerService.save();
		customerService.find();
		
		//基于类的
		productService.save();
		productService.find();
	}

}

3.2 编写通知,配置切面

1.编写通知类,在通知类添加@Aspect 注解,代表这是一个切面类,并将切面类交给spring管理(能被spring扫描到@Component)。

  • @Component(“myAspect”):将增强的类交给spring管理,才可以增强
  • @Aspect:将该类标识为切面类(这里面有方法进行增强),相当于<aop:aspect ref=”myAspect”>
//advice通知类增强类
@Component("myAspect")//相当于<bean id="myAspect" class="cn.itcast.spring.a_aspectj.MyAspect"/>
@Aspect//相当于<aop:aspect ref="myAspect">
public class MyAspect {

}

2.在切面的类,通知方法上添加
@AspectJ提供不同的通知类型

  • @Before 前置通知,相当于BeforeAdvice
  • @AfterReturning 后置通知,相当于AfterReturningAdvice
  • @Around 环绕通知,相当于MethodInterceptor
  • @AfterThrowing抛出通知,相当于ThrowAdvice
  • @After 最终final通知,不管是否异常,该通知都会执行
  • @DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)

3.在spring容器中开启AspectJ 注解自动代理机制
使用<aop:aspectj-autoproxy/>
作用:能自动扫描带有@Aspect的bean,将其作为增强aop的配置,有点相当于:<aop:config>

<!-- 1。确定目标 -->
<!-- 扫描bean组件 -->
<context:component-scan base-package="cn.itcast.spring"/>
<!-- 2:编写通知 -->

<!-- 3:配置aop的aspectj的自动代理:
		自动扫描bean组件中,含有@Aspect的bean,将其作为aop管理,开启动态代理    -->
<aop:aspectj-autoproxy/>

3.2.1 前置通知

在切面的类MyAspect.java类中添加通知方法@Before(),

方案一:可以直接将切入点的表达式写到@Before()中

//前置通知
//相当于:<aop:before method="before" pointcut="bean(*Service)"/>
   //@Before("bean(*Service)"):参数值:自动支持切入点表达式或切入点名字
@Before("bean(*Service)")
public void before(JoinPoint joinPoint){
	System.out.println("=======前置通知。。。。。");
}

方案二:可以使用自定义方法,使用@Pointcut 定义切入点

切入点方法的语法要求:
切点方法:private void 无参数、无方法体的方法,方法名为切入点的名称
一个通知方法@Before可以使用多个切入点表达式,中间使用“||”符合分隔,用来表示多个切入点

//自定义切入点
//方法名就是切入点的名字
//相当于<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
@Pointcut("bean(*Service)")
private void myPointcut(){}
//自定义切入点
//方法名就是切入点的名字
//相当于<aop:pointcut expression="bean(*Service)" id="myPointcut2"/>
@Pointcut("bean(*Service)")
private void myPointcut2(){}


//前置通知
//相当于:<aop:before method="before" pointcut-ref="myPointcut"/>
//相当于:<aop:before method="before" pointcut-ref="myPointcut2"/>
@Before("myPointcut()||myPointcut2()")
public void before(JoinPoint joinPoint){
	System.out.println("=======前置通知。。。。。");
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
回答: Spring AOP提供了四种方式来实现面向切面编程,它们分别是:基于XML的配置方式、基于注解的方式、基于@AspectJ注解的方式和基于编程方式。 1. 基于XML的配置方式:通过在XML配置文件中定义切点和通知,将切面逻辑与业务逻辑分离。可以使用<aop:config>元素来配置切面和通知,<aop:aspect>元素用于定义切面,<aop:pointcut>元素用于定义切点,<aop:before>、<aop:after>等元素用于定义通知。 2. 基于注解的方式:通过在Java类中使用注解来定义切面和通知。可以使用@Aspect注解来定义切面,@Pointcut注解来定义切点,@Before、@After等注解来定义通知。需要在Spring配置文件中启用注解支持,可以使用<context:annotation-config>或者<aop:aspectj-autoproxy>元素来实现。 3. 基于@AspectJ注解的方式:与基于注解的方式类似,但是使用了更强大的@AspectJ注解来定义切面和通知。可以使用@Aspect注解来定义切面,@Pointcut注解来定义切点,@Before、@After等注解来定义通知。需要在Spring配置文件中启用@AspectJ支持,可以使用<aop:aspectj-autoproxy>元素来实现。 4. 基于编程方式:通过编写Java代码来实现切面和通知。可以使用ProxyFactoryBean类来创建代理对象,使用Advice接口的实现类来定义通知。可以在Java类中直接编写切面逻辑,也可以通过实现MethodInterceptor接口来编写通知逻辑。 这些方式都可以实现面向切面编程,选择哪种方式取决于具体的需求和个人偏好。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值