首先我们应该想想为什么要使用aop面向切面编程?面向切面的底层实现是什么?小编在这里举个例子吧
小编首先给出Spring全家桶,方便大家下载使用---->Spring全家桶
1.自定义代理对象代理类以及实现类
1.1 定义接口(ArithmeticCacluetator)
public interface ArithmeticCacluetator {
/*
定义加减乘除四个方法
*/
public void add(int i , int j);
public void sub(int i , int j);
public void mul(int i , int j);
public void div(int i , int j);
}
1.2 创建实现类(ArithmeticCacluetatorImpl),并且这里在每一个方法都输出了操作日志
public class ArithmeticCacluetatorImpl implements ArithmeticCacluetator {
@Override
public void add(int i, int j) {
System.out.println(" i : "+ i + " + " + " j : " + j + " = " + (i + j));
}
@Override
public void sub(int i, int j) {
System.out.println(" i : "+ i + " - " + " j : " + j + " = " + (i - j));
}
@Override
public void mul(int i, int j) {
System.out.println(" i : "+ i + " * " + " j : " + j + " = " + (i * j));
}
@Override
public void div(int i, int j) {
System.out.println(" i : " + i + " / " + " j : " + j + " = " + (i / j));
}
}
1.3 创建测试类:
ArithmeticCacluetator cacluetator = new ArithmeticCacluetatorImpl();
cacluetator.add(1,3);
cacluetator.sub(1,3);
cacluetator.mul(1,3);
cacluetator.div(1,3);
输出结果:
i : 1 + j : 3 = 4
i : 1 - j : 3 = -2
i : 1 * j : 3 = 3
i : 1 / j : 3 = 0
不难发现,这样一一输出会十分的麻烦,每个方法里面都要去写一个输出的日志,这样还会导致代码实现类的可读性很差,所以我们应当弃用这种写法,而使用代理类来帮助我们实现
1.4 创建代理类(这里默认大家有动态代理基础哈)
public class ArithmeticCacluetatirProxy {
public static void main(String[] args) {
//真实对象
ArithmeticCacluetatorImpl arithmeticCacluetator = new ArithmeticCacluetatorImpl();
//代理对象
ArithmeticCacluetator arithmeticCacluetator1_proxy = (ArithmeticCacluetator) Proxy.newProxyInstance(arithmeticCacluetator.getClass().getClassLoader(), arithmeticCacluetator.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里判断只是用来模拟其他的不同的方法名,以及要执行的对应的日志
if(method.getName().equals("add")){
System.out.println("Welcome to "+method.getName()+" Arithmeticacluetator !!!");
Object obj = method.invoke(arithmeticCacluetator, args);
return obj;
}else{
System.out.println("Welcome to AutoJugeMethod "+method.getName()+" Arithmeticacluetator !!!");
Object obj = method.invoke(arithmeticCacluetator, args);
return obj;
}
}
});
//这里是通过代理类去调用方法
arithmeticCacluetator1_proxy.add(12,13);
arithmeticCacluetator1_proxy.sub(12,13);
arithmeticCacluetator1_proxy.mul(12,13);
arithmeticCacluetator1_proxy.div(12,13);
}
输出结果:
method : add
Welcome to add Arithmeticacluetator !!!
i : 12 + j : 13 = 25
method : sub
Welcome to AutoJugeMethod sub Arithmeticacluetator !!!
i : 12 - j : 13 = -1
method : mul
Welcome to AutoJugeMethod mul Arithmeticacluetator !!!
i : 12 * j : 13 = 156
method : div
Welcome to AutoJugeMethod div Arithmeticacluetator !!!
i : 12 / j : 13 = 0
于是我们通过动态代理的形式,简化了日志的输出,这种编程思想,就称之为面向切面编程(AOP)
2.AOP注解:
@Before | 前置通知,在方法执行之前执行 |
---|---|
@After | 后置通知,在方法执行之后执行 |
@AfterRunning; | 返回通知,在方法返回结果之后执行 |
@AfterThrowing | 异常通知,在方法抛出异常之后 |
@Around | 环绕通知,围绕着方法执行 |
注意:要在Spring中声明AspectJ切面,只需要在I0C容器中将切面声明为Bean实例.当在Spring I0C容器中初始化AspectJ切面之后,Spring I0C容器就会为那些与AspectJ切面相匹配的Bean创建代理.
2.1 搭建基本环境(要声明为IOC容器再声明切面才有效)
2.1.1 创建LogginAspect 类,首先声明为IOC容器(@Componet),再次声明为切面(@Aspect)创建beforeInfo方法。
1.添加注解@Before(在方法执行之前执行 ),所以我们应该是最先看到这个方法里面的内容输出。
2.表达式:execution([方法可见性] xx(具体类全路径).xx(方法) ) 标识在xx包下的xx方法(是否包含参数 如果是包含参数就写出对应的参数类型【int , int …】 , 如果是任意参数就传入 ‘*’[ . . ])
@Component
@Aspect
public class LogginAspect {
//参数:
//JoinPoint 切入点
@Before("execution(* cn.itcast.spring.aop.Impl.Service.ArithmeticCacluetatorImpl.*(..))")
public void beforeInfo(JoinPoint joinpoint) {
String name = joinpoint.getSignature().getName();//获取方法名称
Object[] args = joinpoint.getArgs();//获取传入的参数
System.out.println("before... " + " name : " + name);
System.out.println("args : " + Arrays.asList(args));
}
2.1.2 创建spring(bean-aop.xml)配置文件中配置扫描,并且开启aop切面代理模式
<context:component-scan base-package="cn.itcast.spring.aop.Impl.services"/>
<aop:aspectj-autoproxy/>
2.1.3 编写测试类AspectTestDemo
// 创建IOC
ApplicationContext context = new ClassPathXmlApplicationContext("bean-aop.xml");
ArithmeticCacluetator bean = context.getBean(ArithmeticCacluetator.class);
System.out.println(bean.getClass().getName()); //这里获取bean的类型
bean.add(121,131);
为了清楚测试结果,小编将实现类(ArithmeticCacluetatorImpl)中日志输出全部都删除了,现在测试的是add方法,所以小编在add方法中打印一句话
@Override
public void add(int i, int j) {
System.out.println("我是add方法");
}
@Override
public void sub(int i, int j) {
}
@Override
public void mul(int i, int j) {
}
@Override
public void div(int i, int j) {
}
输出结果:
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
不难发现,我们在方法中输出的这句话是最后面才输出的,所以@Before注解是在方法执行前调用的
2.2 @After后置通知注解配置使用
@After("execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetator.*(..))")
public void afterMethod(JoinPoint joinpoint) {
String name = joinpoint.getSignature().getName();
System.out.println("after... " + name);
}
然后再次跑一次测试类:输出结果如下
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add
2.3 @AfterReturning返回值通知注解配置使用。
注意:
1.只有当没有发生异常时,才会执行此方法。2.添加returning = value 参数 用来接受返回值。3.在方法中声明Object value 返回值
/**
只有当成功了才会
@param joinpoint
@param result
*/
@AfterReturning(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",returning = "result")
public void returnMethod(JoinPoint joinpoint,Object result) {
String name = joinpoint.getSignature().getName();
System.out.println("return... " + name + " : " + result);
}
调用测试方法:
输出结果:
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add
return… add : null
因为小编的add方法是void类型,所以这里没有返回值,返回的是null。这里小编临时性的将add方法的返回值改为int类型,再次查看输出结果如下:
@Override
public int add(int i, int j) {
System.out.println("我是add方法");
return i+j;
}
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add
return… add : 252
2.4 @AfterThrowing异常通知注解配置使用。
注意:1.只会在指定的异常出现时才会触发。2.定义throwing = value 设置异常参数名称。3.在方法中声明 (异常类型 value)这里小编声明的是空指针异常
/**
* 指定什么异常 就在什么异常发生时执行
* @param joinPoint
* @param ex
*/
@AfterThrowing(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",
throwing = "ex")
public void throwException(JoinPoint joinPoint , NullPointerException ex){
System.out.println("ERROR ... " + ex);
}
2.4.1 修改实现类中的除法方法,自定义一个数学异常,来测试异常声明
@Override
public void div(int i, int j) {
int c = i / j;
}
2.4.2 调用div方法,传入参数【100,0】
输出结果:
com.sun.proxy.$Proxy19
before… name : div
args : [100, 0]
after… div
Exception in thread “main” java.lang.ArithmeticException: / by zero
2.4.3 修改throwException方法中的异常声明为Exception
@AfterThrowing(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",
throwing = "ex")
public void throwException(JoinPoint joinPoint , Exception ex){
System.out.println("ERROR ... " + ex);
}
输出结果:
com.sun.proxy.$Proxy19
before… name : div
args : [100, 0]
after… div
ERROR … java.lang.ArithmeticException: / by zero
2.4.4 @Pointcut注解的使用。1.同类目录下可直接引用注解。2.异类目录下需要写全路径。3.好处:因为小编刚才演示的时候每一次都要写一次表达式,并且表达式都是一模一样的,这样就写了很多重复的代码,做着同样的事,避免麻烦,我们可以使用@Pointcut注解来声明表达式,可以在需要的地方直接引用即可
@Pointcut("execution(* cn.itcast.spring.aop.Impl.Order.Service.ArithmeticCacluetator.*(..))")
public void declareExecutionExpression(){};
@Before("declareExecutionExpression()")
public void sayMehtod(){
System.out.println(" VildationAspect aop ... ");
}
2.4.5 异类下的的引用:
@Before("cn.itcast.spring.aop.Impl.Order.VildationAspect.declareExecutionExpression()")
public void beforeMethod(){
System.out.println("before Method ... ");
}
2.5 @Around环绕通知注解使用(基于动态代理 , 小编这里单独将环绕通知提取出来了,不会受到之前配置过的四个通知的影响)。注意:1.切入点应该声明为ProceedingJoinPoint。2.声明返回值为Object类型。
/**
可以获取 String method;
@param jdp
@return
*/
@Around("execution(* cn.itcast.spring.aop.Impl.Around.Services.ArithmeticCacluetatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint jdp){
Object result = null;
String name = jdp.getSignature().getName();
try {
//前置通知
System.out.println("Around Before ... getArgs ... " + Arrays.asList(jdp.getArgs()));
result = jdp.proceed(); //method.invoke(); 代理对象调用
//后置通知
System.out.println("Around After .... " + name);
//返回通知
System.out.println("Around AfterReturing ... " + result);
}catch (Throwable e){
//出现异常,这里不选择手动抛出,用控制台打印
System.out.println("The Method : " + name +"Error Appentend ... " + e);
}
return result;
}
2.5.1 调用实现类中的div方法
bean.div(10,0);
输出结果:
Around Before … getArgs … [10, 0]
The Method : divError Appentend … java.lang.ArithmeticException: / by zero
这里出现了异常,仅仅只执行了前置通知,后面的通知都没有被执行,就直接走进catch中去了
2.5.2 在接口中添加public String show(String name)方法,实现类实现
@Override
public String show(String name) {
System.out.println(name+" comming...");
return name;
}
2.5.3 执行show(“Audi”)方法
测试结果如下:
Around Before … getArgs … [Audi]
Audi comming…
Around After … show
Around AfterReturing … Audi
3.AOP 使用XML形式实现:
3.1 因为是在XML中声明使用,所以我们便不需要再添加注解。创建spring配置文件使用(bean-application-xml.xml)这里需要用到aop,context标签,将这段复制到XML就可以使用了
<?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:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
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/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
</beans>
3.2 声明所有使用到的Bean
<!-- 配置使用到的bean -->
<bean id="logginAspect" class="cn.itcast.spring.aop_xml.LogginAspect"/>
<bean id="vildationAspect" class="cn.itcast.spring.aop_xml.VildationAspect"/>
<bean id="arithmeticCacluetator" class="cn.itcast.spring.aop_xml.Service.ArithmeticCacluetatorImpl"></bean>
<bean id="logginAround" class="cn.itcast.spring.aop.Impl.Around.LogginAround"></bean>
3.3 切面类LogginAspect
public class LogginAspect {
public void beforeInfo(JoinPoint joinpoint) {
String name = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
System.out.println("before... " + " name : " + name);
System.out.println("args : " + Arrays.asList(args));
}
public void afterMethod(JoinPoint joinpoint) {
String name = joinpoint.getSignature().getName();
System.out.println("after... " + name);
}
public void returnMethod(JoinPoint joinpoint,Object result) {
String name = joinpoint.getSignature().getName();
System.out.println("return... " + name + " : " + result);
}
public void throwException(JoinPoint joinPoint , NullPointerException ex){
System.out.println("ERROR ... " + ex);
}
}
3.4 VildationAspect类(设置了Order属性,方法是最先被执行的)
public void sayMehtod(){
System.out.println(" VildationAspect aop ... ");
}
3.5 配置程序一致:
<aop:config>
<!--
配置的基本逻辑:
1.配置pointcut 公共指向
2.配置Aspect 注解 ref - 涉及到的类
3.配置 [ 前置 , 后置 , 返回值 , 抛出异常 , 环绕通知]
-->
<aop:pointcut id="declearExpression" expression="execution(* cn.itcast.spring.aop_xml.Service.ArithmeticCacluetator.*(..))"></aop:pointcut>
<!-- 切面作用在哪个类上 相当于注解@Aspect -->
<aop:aspect ref="logginAspect">
<!-- 基本配置 -->
<aop:before method="beforeInfo" pointcut-ref="declearExpression"/>
<aop:after method="afterMethod" pointcut-ref="declearExpression"/>
<!-- 这里注意参数一定要对应 -->
<aop:after-returning method="returnMethod" pointcut-ref="declearExpression" returning="result"/>
<aop:after-throwing method="throwException" pointcut-ref="declearExpression" throwing="ex"/>
</aop:aspect>
<!-- 配置优先级别 -->
<aop:aspect ref="vildationAspect" order="1">
<aop:before method="sayMehtod" pointcut-ref="declearExpression"/>
</aop:aspect>
<aop:aspect ref="logginAround">
<aop:around method="aroundMethod" pointcut-ref="declearExpression"></aop:around>
</aop:aspect>
</aop:config>
3.6 测试类:
ArithmeticCacluetator bean = (ArithmeticCacluetator) context.getBean("arithmeticCacluetator");
System.out.println(bean);
int result = bean.add(12, 3);//返回值为int类型
System.out.println("result : " + result);
输出结果:
VildationAspect aop … 设置了Order属性,所以sayMethod方法最先被执行
Around Before … getArgs … [12, 3] //前置通知
Around After … add // 后置通知
Around AfterReturing … 15 //返回值通知
result : 15 //自己打印的返回值
好了,这里就是AOP面向切面编程的内容了,虽然有点抽象,但是相信通过不断地学习,可以不断地去了解它