AOP面向切面编程

1、 面向切面编程AOP

  1. 作用:在不改变程序代码的基础上进行功能增强。
  2. 原理:Proxy代理。即假如业务是从淘宝网买书,那么淘宝网只需要把数放在包裹并填写地址,剩下的任务由快递公司完成,在这个过程中,快递公司就是淘宝网的代理。

2、AOP的原理是代理设计模式

java中实现动态代理可以使用四种方式:

  1. JDK提供的动态代理(原理是实现接口)
  2. 使用cglib框架(原理是继承)
  3. 使用javaassist框架(原理是继承)
  4. 使用Spring框架(原理是实现接口)
    在这里插入图片描述

2.1 静态代理的实现

概述

代理对象与被代理对象必须实现一个接口,保留被代理对象的接口样式,保持接口不变原则。

实现

项目结构
proxy.SFSendBook:

package com.wqq.www.proxy.proxy;

import com.wqq.www.proxy.service.ISendBook;

public class SFSendBook implements ISendBook {
    private ISendBook who;

    public SFSendBook(ISendBook who) {
        this.who = who;
    }

    @Override
    public void sendBook() {
        System.out.println("顺丰接收书籍,从数据库取得订单信息");
        who.sendBook();
        System.out.println("顺丰开始送书,最终送达");
    }
}

DangDang:

package com.wqq.www.test1.service;

public class DangDang implements ISendBook{

    @Override
    public void SendBook() {
        System.out.println("从数据库取得订单信息");
        System.out.println("当当保存了订单信息");
        System.out.println("当当配送");
    }
}

ISendBook:

package com.wqq.www.proxy.service;

public interface ISendBook {
    void sendBook();
}

JD:

package com.wqq.www.proxy.service;

public class JD implements ISendBook{

    @Override
    public void sendBook() {
        System.out.println("京东保存数据库订单信息");
    }
}

Test1:

package com.wqq.www.proxy.test;

import com.wqq.www.proxy.proxy.SFSendBook;
import com.wqq.www.proxy.service.DangDang;
import com.wqq.www.proxy.service.ISendBook;

public class Test1 {
    public static void main(String[] args) {
        ISendBook sendBook = new DangDang();
        SFSendBook sfSendBook = new SFSendBook(sendBook);
        sfSendBook.sendBook();
    }
}

运行结果:
在这里插入图片描述

2.2 动态代理

概述

java中的动态代理类的对象由Proxy.java类的newProxyInstance方法进行创建。它不是由程序员自己创建的,而是由jvm创建的。
增强类算法需要实现InvocationHandler接口来进行实现。
项目结构

实现

handler.MyHandler:

package com.wqq.www.proxyDynamic.handler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyHandler implements InvocationHandler {

    private Object who;

    public MyHandler(Object who) {
        this.who = who;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开启事务");
        method.invoke(who);
        System.out.println("结束事务");
        return null;
    }
}

test.Test1:

package com.wqq.www.proxyDynamic.test;

import com.wqq.www.proxyDynamic.handler.MyHandler;
import com.wqq.www.proxyDynamic.serviceBook.DangDang;
import com.wqq.www.proxyDynamic.serviceBook.ISendBook;
import sun.management.Sensor;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test1 {
    public static void main(String[] args) {
        DangDang dangDang = new DangDang();

        ClassLoader classLoader = Test1.class.getClassLoader();
        Class<?>[] intefaces =  dangDang.getClass().getInterfaces();
        InvocationHandler handler = new MyHandler(dangDang);
        Object o = Proxy.newProxyInstance(classLoader,intefaces, handler);
        ISendBook sendBook = (ISendBook) o;
        sendBook.sendBook();
    }
}

运行结果:
在这里插入图片描述

service包的类与上一个案例相同。

总结

  1. 代理类由jvm创建,不需要程序员手动创建,减少编码量。
  2. 代理类不再绑死固定接口,达到代理类与接口解耦。
  3. 再InvocationHandler中通过反射技术可以更灵活的处理增强算法。

注:Spring虽使用反射原理,但不支持Field字段级的增强。此例中的invoke方法返回null会导致该方法实例化的toString返回的是null,hashcode方法会报空指针错误,想要修复这一类错误请不要返回null而是返回有意义的对象。

2.3 实现多个代理

概述

通过实现代理套代理便可实现多个代理。

代码

    public static void main(String[] args) {
		ClassLoader classLoader = Test1.class.getClassLoader();
		//取得当当的接口,当当的接口也是代理类的接口
		DangDang dangDang = new DangDang();
		Class<?>[] interfaces = dangDang.getClass().getInterfaces();
		InvocationHandler handler = new MyHandler(dangDang);
		//o是使用handler增强过的代理对象
		Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
		//构造第二个增强方法
		InvocationHandler handler2 = new MyHandler2(o);
		//o2在o的基础上二次增强
		Object o2 = Proxy.newProxyInstance(classLoader, interfaces, handler2);
		//o2仍然可以强转为ISendBook接口,此时调用便可以实现多个代理。
		ISendBook sendBook = (ISendBook) o2;
		sendBook.SendBook();
	}

2.4使用Spring实现动态代理

概述

虽然jdk的动态代理能实现目标对象的功能增强,但是仍然不够方便,且程序员的写法不同也会使得代码风格不一致从而难以后期维护。
好在Spring框架对动态代理进行了封装,产生了自己的AOP框架,称为SpringAOP,该框架的常见接口如下:

  1. 方法前增强实现org.springframework.aop.MethodBeforeAdvice接口
  2. 方法后增强实现
    org.springframework.aop.AfterReturningAdvice接口
  3. 方法环绕增强实现
    org.springframework.aop.MethodInterceptor接口
  4. 异常处理增强实现
    org.springframework.aop.ThrowsAdvice接口

代码实现

项目结构:
项目结构

在这里插入图片描述
注意applicationContext的创建方法,右键Resource文件夹(没有的右键main->new->directory->Resources进行创建),按照一下的顺序生成(也要记得引用spring相关依赖)。
在这里插入图片描述
MyAfterReturningAdvice :

package com.wqq.www.test53.advice;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class MyAfterReturningAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("afterReturning");
    }
}

MyMethodBeforeAdvice :

package com.wqq.www.test53.advice;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MethodBeforeAdvice");
    }
}

MyMethodInterceptor :

package com.wqq.www.test53.advice;

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

import java.sql.SQLOutput;

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("MethodInterceptor begin");
        Object returnValue = methodInvocation.proceed();
        System.out.println("MethodInterceptor end");
        return returnValue;
    }
}

MyThrowsAdvice :

package com.wqq.www.test53.advice;

import org.springframework.aop.ThrowsAdvice;

import java.lang.reflect.Method;

public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
        System.out.println("ThrowsAdvice method.getName()=" + method.getName() + " target=" + target + " ex.getMessage()=" + ex.getMessage() + " save db");
    }
}

service.DB:

package com.wqq.www.test53.service;

public class DB implements com.wqq.www.test53.service.ISaveData {
    @Override
    public void ok() {
        System.out.println("ok");
    }

    @Override
    public void no() {
        System.out.println("no");
        Integer.parseInt("a");
    }
}

service.ISaveData :

package com.wqq.www.test53.service;
public interface ISaveData {
    public void ok();

    public void no();
}

applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="myAfterReturningAdvice" class="com.wqq.www.test53.advice.MyAfterReturningAdvice"></bean>
    <bean id="myMethodBeforeAdvice" class="com.wqq.www.test53.advice.MyMethodBeforeAdvice"></bean>
    <bean id="myMethodInterceptor" class="com.wqq.www.test53.advice.MyMethodInterceptor"></bean>
    <bean id="myThrowsAdvice" class="com.wqq.www.test53.advice.MyThrowsAdvice"></bean>

    <bean id="db" class="com.wqq.www.test53.service.DB"></bean>

    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interfaces">
            <value>com.wqq.www.test53.service.ISaveData</value>
        </property>
        <property name="target" ref="db"></property>
        <property name="interceptorNames">
            <list>
                <value>myAfterReturningAdvice</value>
                <value>myMethodBeforeAdvice</value>
                <value>myMethodInterceptor</value>
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>
</beans>

Test1 :

package com.wqq.www.test53.test;

import com.wqq.www.test53.service.ISaveData;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test1 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        ISaveData saveData = (ISaveData) ac.getBean("proxy");
        saveData.ok();
    }
}

运行结果:
在这里插入图片描述

package com.wqq.www.test53.test;

import com.wqq.www.test53.service.ISaveData;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test2 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        ISaveData saveData = (ISaveData) ac.getBean("proxy");
        saveData.no();
    }
}

运行结果:
在这里插入图片描述

3、与AOP相关的必备概念

1. 横切关注点Cross-cutting Concerns

横切关注点就是通用性功能,比如:

  1. 方法执行时间记录
  2. 方法权限验证
  3. 常规日志记录
  4. 数据库事务处理

2. 切面与面向切面编程

在这里插入图片描述
三个业务类都需要3个切面中的功能,所以把这些功能提取出来,当业务类的代码执行时动态的对切面中的功能代码进行调用,便实现了对“横切关注点”通用性代码的复用,达到了解耦的目的。AOP可以让一组类的对象具有相同的行为。

(1)切面

对“横切关注点”的模块化,也就是将“横切关注点”的功能代码提取出来放入一个单独的类中进行统一处理,这个类就是切面类,也可称为切面Aspect

(2)AOP编程(Aspect Oriented Programming面向切面编程)

主要就是针对切面进行设计代码。

3.连接点 Join Point

连接点是在软件执行过程中能够插入切面的一个点(可以是调用方法前、调用方法后、方法抛出异常时、方法返回了值后等位置)。

4.切点 Pointcut

大多数情况下只针对部分连接点应用切面,这些部分连接点称为切点。(不可能在所有连接点都应用切面,会拖慢程序运行效率。)

5.通知 Advice

通知是应用切面的时机。
在SpringAOP中,通知分为5种:
(1)前置通知(before):方法被调用之前。
(2)后置通知(after):方法被调用之后。
(3)环绕通知(around):方法被调用之前与之后。
(4)返回通知(after-returning):方法返回了值。
(5)异常通知(after-throwing):方法出现了异常。

6.织入 Weaving

织入就是把切面(干什么)、切点(在哪干)、通知(什么时候干)整合起来,应用到目标对象中。

4. 面向切面编程AOP核心案例

(0)切面表达式

在这里插入图片描述execution(modifiers-pattern?
ret-type-pattern
declaring-type-pattern?
name-pattern(param-pattern)?
throws-pattern?)

(1) modifiers-pattem代表修饰符,比如public,private。
(2)ret-type-pattem 代表返回值类型
(3)declaring-type-pattern代表包路径
(4)name-pattern 代表方法
(5)param-pattern参数
(6)throws-pattern代表异常
带?问号的表示可以忽略,也就是除了ret-type-pattern之外,其它都是可选的。

(1)实现前置-后置-返回-异常通知

项目结构:
在这里插入图片描述
aspect.Myaspect:

package com.wqq.www.aopTest54.aspect;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect {
    @Before("execution(* com.wqq.www.aopTest54.service.*.*(..))")
//    @Before("execution(* com.wqq.www.aopTest54.service.*.*(..)")
    public void before(){
        System.out.println("before");
    }
    @After("execution(* com.wqq.www.aopTest54.service.*.*(..))")
    public void after(){
        System.out.println("after");
    }
    @AfterReturning("execution(* com.wqq.www.aopTest54.service.*.*(..))")
    public void afterReturning(){
        System.out.println("afterReturning");
    }
    @AfterThrowing("execution(* com.wqq.www.aopTest54.service.*.*(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }

}

javaconfig.JavaConfig:

package com.wqq.www.aopTest54.javaconfig;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class JavaConfig {
}

service.DB:

package com.wqq.www.aopTest54.service;

import org.springframework.stereotype.Component;

@Component
public class DB {
    public void ok(){
        System.out.println("ok");
    }

    public void no(){
        System.out.println("no");
        Integer.parseInt("a");
    }

}

test.Test1:

package com.wqq.www.aopTest54.test;

import com.wqq.www.aopTest54.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test1 {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext("com.wqq.www.aopTest54");
        DB db = ac.getBean(DB.class);
        db.ok();
    }
}

运行结果:
在这里插入图片描述

test.Test2:

package com.wqq.www.aopTest54.test;

import com.wqq.www.aopTest54.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test2 {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext("com.wqq.www.aopTest54");
        DB db = ac.getBean(DB.class);
        db.no();
    }
}

运行结果:
在这里插入图片描述

有接口时的spring aop编程

项目结构:除了service和test包其余无变化。
在这里插入图片描述
service.ISaveData:

package com.wqq.www.aopTest55.service;

public interface ISaveData {
    public void save();
}
service.DB:
package com.wqq.www.aopTest55.service;

import org.springframework.stereotype.Component;

@Component
public class DB implements ISaveData{
    public void ok(){
        System.out.println("ok");
    }

    public void no(){
        System.out.println("no");
        Integer.parseInt("a");
    }

    @Override
    public void save() {
        System.out.println("save in db");
    }
}

test.Test1:

package com.wqq.www.aopTest55.test;

import com.wqq.www.aopTest55.service.DB;
import com.wqq.www.aopTest55.service.ISaveData;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test1 {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext("com.wqq.www.aopTest55");
        ISaveData saveData = (ISaveData) ac.getBean("DB");
        saveData.save();
    }
}

注意:如果业务类实现了接口,在获得业务类时,必须使用context.getBean(“beanld”)的写法,不能使用context.getBean(Bean.class)的写法,不然会出现找不到JavaBean的异常。出现异常的原因是因为根据context.getBean(Bean.class)写法获得的对象的真实数据类型是Bean的代理类BeanProxy,Bean.class和BeanProxy.class 不是同一个类型,所以出现异常。

运行结果(与上个案例一致):
在这里插入图片描述

总结

无异常的情况下运行顺序:
(1)前置通知@Before
(2)业务类中的方法
(3)返回通知@AfterReturning
(4)后置通知@After
(5)返回值
有异常的情况下运行顺序:
(1)前置通知@Before
(2)业务类中的方法
(3)异常通知@AfterThrowing
(4)后置通知@After

(2)对前置通知-后置通知-返回通知-异常通知传入JoinPoint参数

概述

参数JoinPoint可以实现对连接点信息的获取

项目与代码

项目结构:
在这里插入图片描述
aspect.MyAspect:

package com.wqq.www.test57.aspect;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect
public class MyAspect {
    //任意返回值类型      对指定包中的包        任意类进行切面处理     任意名称方法 任意类型参数
    @Before("execution(* com.wqq.www.test57.service.*.*(..))")
//    @Before("execution(* com.wqq.www.aopTest54.service.*.*(..)")
    public void before(JoinPoint joinPoint){
        System.out.println("before");
        System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
    }
    @After("execution(* com.wqq.www.test57.service.*.*(..))")
    public void after(JoinPoint joinPoint){
        System.out.println("after");
        System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
    }
    @AfterReturning("execution(* com.wqq.www.test57.service.*.*(..))")
    public void afterReturning(JoinPoint joinPoint){
        System.out.println("afterReturning");
        System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
    }
    @AfterThrowing("execution(* com.wqq.www.test57.service.*.*(..))")
    public void afterThrowing(JoinPoint joinPoint){
        System.out.println("afterThrowing");
        System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
    }

}

javaConfig.JavaConfig:(@EnableAspectJAutoProxy使允许切面方法)

package com.wqq.www.test57.javaconfig;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class JavaConfig {
}

service.DB:

package com.wqq.www.test57.service;

import org.springframework.stereotype.Component;

@Component
public class DB implements ISaveData{
    @Override
    public void ok() {
        System.out.println("ok");
    }

    @Override
    public void no() {
        System.out.println("no");
        Integer.parseInt("a");
    }
}

service.ISaveData:

package com.wqq.www.test57.service;

import org.springframework.stereotype.Component;

@Component
public interface ISaveData {
    void ok();
    void no();
}

test.Test1:

package com.wqq.www.test57.test;
import com.wqq.www.test55.advice.MyMethodInterceptor;
import com.wqq.www.test56.service.DB;
import com.wqq.www.test57.service.ISaveData;
import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test1 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test57");
        ISaveData saveData = (ISaveData) applicationContext.getBean("DB");
        saveData.ok();
        System.out.println();
        System.out.println();
        saveData.no();
    }
}

运行结果:
在这里插入图片描述

(3)实现环绕通知

项目结构及除了MyAspect以外的类代码相同。
MyAspect:

package com.wqq.www.test58.aspect;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect
public class MyAspect {

    @Around("execution(* com.wqq.www.test58.service.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object returnValue = null;
        System.out.println("begin");
        returnValue = joinPoint.proceed();
        System.out.println("end");
        return returnValue;
    }
}

运行结果:
在这里插入图片描述

(4)使用bean表达式

概述

使用bean表达式可以限制切面应用于的目标对象。

项目结构与代码
关键代码

切面表达式中使用 &&bean(bean的id)来指定切面对哪个bean起作用

@Around("execution(* com.wqq.www.test59.service.*.*(..)) && bean(b)")
结构与代码

项目结构:
在这里插入图片描述aspect.MyAspect:

package com.wqq.www.test59.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect {

    @Around("execution(* com.wqq.www.test59.service.*.*(..)) && bean(b)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object returnValue = null;
        System.out.println("begin");
        returnValue = joinPoint.proceed();
        System.out.println("end");
        return returnValue;
    }

}

javaconfig.JavaConfig:

package com.wqq.www.test59.javaconfig;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class JavaConfig {
}

service.SaveService1:

package com.wqq.www.test59.service;

import org.springframework.stereotype.Service;

@Service("a")
public class SaveService1 {
    public void ok(){
        System.out.println("SaveService1 ok");
    }
}

service.SaveService2:

package com.wqq.www.test59.service;

import org.springframework.stereotype.Service;

@Service("b")
public class SaveService2 {
    public void ok(){
        System.out.println("SaveService2 ok");
    }
}

test.Test1:

package com.wqq.www.test59.test;

import com.wqq.www.test59.service.SaveService1;
import com.wqq.www.test59.service.SaveService2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test1 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test59");
        SaveService1 a = (SaveService1) applicationContext.getBean(SaveService1.class);
        a.ok();
        SaveService2 b = (SaveService2) applicationContext.getBean(SaveService2.class);
        b.ok();
    }
}

运行结果:
在这里插入图片描述

(5)使用@PointCut定义全局切点

概述

前面4个部分多处使用相同的execution表达式,可以将execution进行全局化,以减少冗余的配置。@PointCut注解就是实现这样的功能的。

项目结构与代码
关键代码

@Pointcut进行全局定义,其他方法通过注解下发的函数进行调用即可减少代码冗余。

@Pointcut("execution(* com.wqq.www.test60.service.*.*(..))")
public void publicPointCut(){}

@Before("publicPointCut()")
public void before(){
    System.out.println("before");
}
详细代码

项目结构:
在这里插入图片描述:

aspect.MyAspect :

package com.wqq.www.test60.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;

@Component
@Aspect
public class MyAspect {
    @Pointcut("execution(* com.wqq.www.test60.service.*.*(..))")
    public void publicPointCut(){}
    
    @Before("publicPointCut()")
    public void before(){
        System.out.println("before");
    }
    @After("publicPointCut()")
    public void after(JoinPoint joinPoint){
        System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
    }
    @AfterReturning("publicPointCut()")
    public void afterReturning(JoinPoint joinPoint){
        System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
    }
    @AfterThrowing("publicPointCut()")
    public void afterThrowing(JoinPoint joinPoint){
        System.out.println("对象:"+joinPoint.getThis() + "方法:" + joinPoint.getSignature().getName()+"参数:"+ Arrays.toString(joinPoint.getArgs()));
    }

}

JavaConfig与之前相同。
service.DB:

package com.wqq.www.test60.service;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Service
public class DB implements ISaveData {
    @Override
    public void ok(String username,String password)
    {
        System.out.println("username = "+username + ",password = "+password);
        System.out.println("ok");
    }

    @Override
    public void no() {
        System.out.println("no");
        Integer.parseInt("a");
    }
}

service.ISaveData:

package com.wqq.www.test60.service;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Service
public interface ISaveData {
    void ok(String username,String password);
    void no();
}

test.Test1:

package com.wqq.www.test60.test;
import com.wqq.www.test60.service.ISaveData;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test1 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test60");
        ISaveData saveData = (ISaveData) applicationContext.getBean("DB");
        saveData.ok("中国","中国人");
        System.out.println();
        saveData.no();
    }
}

运行结果:
在这里插入图片描述

(6)向切面传入参数

概述

前面的方法可以在各个连接点实现切面的功能,但是它们与我们自己写的代码缺少一种关联。
如果切面类能获得我们方法的参数值,我们便可以建立一种关联,这样就可以进行一些日志的输出了!

项目结构与代码
关键代码

首先在注解中声明参数类型并使用&&args声明参数的别名,然后在下方的切点构造方法中使用这些类型和别名进行构造。
后面在使用时,在对应方法的注解中注明形参,并在下方的方法使用这些形参。注意下面方法的形参和类型必须与之前定义的一致!
而且DB.Test2与公共切点注解的参数类型应该一致!

下面的两幅图片描述了四组对应关系:

在这里插入图片描述在这里插入图片描述涉及代码如下:

@Pointcut("execution(* com.wqq.www.test61.service.DB.Test2(String,String,int,java.util.Date))&&args(u,p,a,i)")
    public void publicPointCut2(String u, String p, int a, Date i){}
    @Before("publicPointCut2(usernamePara,passwordPara,agePara,insertDatePara)")
    public void before2(String usernamePara,String passwordPara,int agePara,Date insertDatePara){
        System.out.println("before2 username="+usernamePara + "password="+passwordPara+"age="+agePara+"date="+insertDatePara);
    }
    //这是另一个文件DB.java的代码
    public void Test2(String username, String password, int age, Date insertDate){
        System.out.println("service username" + username);
        System.out.println("service password" + password);
        System.out.println("service age" + age);
        System.out.println("service insertDate" + insertDate);
    }    
详细代码

项目结构:
在这里插入图片描述aspect.MyAspect:

package com.wqq.www.test61.aspect;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Aspect
public class MyAspect {
    @Pointcut("execution(* com.wqq.www.test61.service.DB.Test1(String))&&args(xxx)")
    public void publicPointCut(String xxx){}

    @Pointcut("execution(* com.wqq.www.test61.service.DB.Test2(String,String,int,java.util.Date))&&args(u,p,a,i)")
    public void publicPointCut2(String u, String p, int a, Date i){}
    @Before("publicPointCut(usernamePara)")
    public void before(String usernamePara){
        System.out.println("before1 : username" + usernamePara);
    }
    @Before("publicPointCut2(usernamePara,passwordPara,agePara,insertDatePara)")
    public void before2(String usernamePara,String passwordPara,int agePara,Date insertDatePara){
        System.out.println("before2 username="+usernamePara + "password="+passwordPara+"age="+agePara+"date="+insertDatePara);
    }
}

JavaConfig与之前相同

service.DB:

package com.wqq.www.test61.service;

import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class DB {
    public void ok(String username,String password)
    {
        System.out.println("username = "+username + ",password = "+password);
        System.out.println("ok");
    }

    public void no() {
        System.out.println("no");
        Integer.parseInt("a");
    }

    public void Test1(String username){
        System.out.println("service name="+username);
    }

    public void Test2(String username, String password, int age, Date insertDate){
        System.out.println("service username" + username);
        System.out.println("service password" + password);
        System.out.println("service age" + age);
        System.out.println("service insertDate" + insertDate);
    }
}

test.Test1:

package com.wqq.www.test61.test;

import com.wqq.www.test61.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Date;

public class Test1 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test61");
        DB db = applicationContext.getBean(DB.class);
        db.Test1("中国");
        System.out.println();
        db.Test2("中国","中国人" ,100 ,new Date());

    }
}

运行结果:
可以看到参数传入切面方法中,并且切面方法对这些参数进行了输出。
在这里插入图片描述

(7)使用@AfterReturning和@AfterThrowing向切面传入参数

概述

@AfterReturning和@AfterThrowing传参方式分别是向切面方法传入返回值异常信息,与(6)中的传参方式不同。

项目结构与代码
关键代码

通过在注解里面加上returning与throwing属性并在下方方法传入,便可实现切面方法获取返回值和异常信息。

@AfterReturning(value = "publicPointCut1()",returning = "t")
public void afterReturning(Object t){
    System.out.println("afterReturning t="+t);
}
@AfterThrowing(value = "publicPointCut1()",throwing = "t")
public void afterThrowing(Throwable t){
    System.out.println("afterThrowing t="+t.getMessage());
}
详细代码

在这里插入图片描述
aspect.MyAspect:

package com.wqq.www.test62.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Aspect
public class MyAspect {
    @Pointcut("execution(* com.wqq.www.test62.service..*(..))")
    public void publicPointCut1(){}
    @AfterReturning(value = "publicPointCut1()",returning = "t")
    public void afterReturning(Object t){
        System.out.println("afterReturning t="+t);
    }
    @AfterThrowing(value = "publicPointCut1()",throwing = "t")
    public void afterThrowing(Throwable t){
        System.out.println("afterThrowing t="+t.getMessage());
    }
}

JavaConfig与之前相同。
service.DB:

package com.wqq.www.test62.service;

import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class DB {
//注意这里要有返回值
    public Date Test1(String username){
        System.out.println("service name="+username);
        return new Date();
    }
//这是一个错误的方法,测试抛出异常的切面方法传参用的
    public void Test2(String username, String password, int age, Date insertDate){
        System.out.println("service username" + username);
        System.out.println("service password" + password);
        System.out.println("service age" + age);
        System.out.println("service insertDate" + insertDate);
        Integer.parseInt("a");
    }
}

test.Test1:

package com.wqq.www.test62.test;

import com.wqq.www.test62.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Date;

public class Test1 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test62");
        DB db = applicationContext.getBean(DB.class);
        db.Test1("中国");
        System.out.println();
        db.Test2("中国","中国人" ,100 ,new Date());

    }
}

运行结果:
可见返回值和异常信息都通过切面方法进行了输出。
在这里插入图片描述

(8)向环绕通知传入参数

概述

该部分和(6)中的普通通知传参类似,但要注意环绕通知比它们要多一个参数ProceedingJoinPoint,因为环绕通知需要这个参数来确定调用原方法的位置。

项目结构和代码
关键代码
    @Pointcut("execution(* com.wqq.www.test63.service.DB.Test2(String,String,int,java.util.Date))&&args(u,p,a,i)")
    public void publicPointCut2(String u, String p, int a, Date i){}
   @Around("publicPointCut2(username,password,age,date)")
    public Object around2(ProceedingJoinPoint joinPoint,String username,String password,int age,Date date) throws Throwable {
        System.out.println("before2 username="+username + "password="+password+"age="+age+"date="+date);
        Object returnValue = null;
        System.out.println("begin");
        returnValue = joinPoint.proceed();
        System.out.println("end");
        return returnValue;
    }
详细代码

项目结构
在这里插入图片描述
aspect.MyAspect:

package com.wqq.www.test63.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Aspect
public class MyAspect {
    @Pointcut("execution(* com.wqq.www.test63.service.DB.Test1(String))&&args(xxx))")
    public void publicPointCut1(String xxx){}

    @Pointcut("execution(* com.wqq.www.test63.service.DB.Test2(String,String,int,java.util.Date))&&args(u,p,a,i)")
    public void publicPointCut2(String u, String p, int a, Date i){}

    @Around("publicPointCut1(username)")
    public Object around1(ProceedingJoinPoint joinPoint,String username) throws Throwable {
        System.out.println("around1 usernameParam=" + username);
        Object returnValue = null;
        System.out.println("begin");
        returnValue = joinPoint.proceed();
        System.out.println("end");
        return returnValue;
    }

    @Around("publicPointCut2(username,password,age,date)")
    public Object around2(ProceedingJoinPoint joinPoint,String username,String password,int age,Date date) throws Throwable {
        System.out.println("before2 username="+username + "password="+password+"age="+age+"date="+date);
        Object returnValue = null;
        System.out.println("begin");
        returnValue = joinPoint.proceed();
        System.out.println("end");
        return returnValue;
    }
}

JavaConfig与之前相同。
service.DB:

package com.wqq.www.test63.service;

import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class DB {

    public void Test1(String username){
        System.out.println("service name="+username);
    }

    public void Test2(String username, String password, int age, Date insertDate){
        System.out.println("service username" + username);
        System.out.println("service password" + password);
        System.out.println("service age" + age);
        System.out.println("service insertDate" + insertDate);
        Integer.parseInt("a");
    }
}

test.Test1:

package com.wqq.www.test63.test;

import com.wqq.www.test63.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Date;

public class Test1 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test63");
        DB db = applicationContext.getBean(DB.class);
        db.Test1("中国");
        System.out.println();
        db.Test2("中国","中国人" ,100 ,new Date());

    }
}

运行结果:
在这里插入图片描述

(9)实现多切面的应用

概述

多个切面可以存在于多个类中,并一起生效。

项目结构与代码
关键代码

aspect包中有三个类,想让这三个类中的切面都对service.DB.Test1的方法起作用,只需要向每个类都加上该注解即可。

	@Pointcut("execution(* com.wqq.www.test64.service.DB.Test1(String))&&args(xxx))")
    public void publicPointCut1(String xxx){}

    @Around("publicPointCut1(username)")
    public Object around1(ProceedingJoinPoint joinPoint,String username) throws Throwable {
        System.out.println("around1 usernameParam=" + username);
        .....
        return yourVal;
    }
详细代码

项目结构:
在这里插入图片描述
aspect.MyAspect1:

package com.wqq.www.test64.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Aspect
public class MyAspect1 {
    @Pointcut("execution(* com.wqq.www.test64.service.DB.Test1(String))&&args(xxx))")
    public void publicPointCut1(String xxx){}

    @Around("publicPointCut1(username)")
    public Object around1(ProceedingJoinPoint joinPoint,String username) throws Throwable {
        System.out.println("around1 usernameParam=" + username);
        Object returnValue = null;
        System.out.println("begin");
        returnValue = joinPoint.proceed();
        System.out.println("end");
        return returnValue;
    }

}

aspect.MyAspect2:

package com.wqq.www.test64.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect2 {
    @Pointcut("execution(* com.wqq.www.test64.service.DB.Test1(String))&&args(xxx))")
    public void publicPointCut1(String xxx){}

    @Around("publicPointCut1(username)")
    public Object around1(ProceedingJoinPoint joinPoint,String username) throws Throwable {
        System.out.println("around2 usernameParam=" + username);
        Object returnValue = null;
        System.out.println("begin2");
        returnValue = joinPoint.proceed();
        System.out.println("end2");
        return returnValue;
    }

}

aspect.MyAspect3:

package com.wqq.www.test64.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect3 {
    @Pointcut("execution(* com.wqq.www.test64.service.DB.Test1(String))&&args(xxx))")
    public void publicPointCut1(String xxx){}

    @Around("publicPointCut1(username)")
    public Object around1(ProceedingJoinPoint joinPoint,String username) throws Throwable {
        System.out.println("around3 usernameParam=" + username);
        Object returnValue = null;
        System.out.println("begin3");
        returnValue = joinPoint.proceed();
        System.out.println("end3");
        return returnValue;
    }
}

JavaConfig与之前相同。
service.DB:

package com.wqq.www.test64.service;

import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class DB {

    public void Test1(String username){
        System.out.println("service name="+username);
    }
}

test.Test1:

package com.wqq.www.test64.test;

import com.wqq.www.test64.service.DB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Date;

public class Test1 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.wqq.www.test64");
        DB db = applicationContext.getBean(DB.class);
        db.Test1("中国");
    }
}

运行结果:
在这里插入图片描述

(10)使用@Order注解指定切面运行顺序

代码

该部分与上例的代码基本相同,不同的是aspect中的类都加上了Order注解。

@Component
@Aspect
@Order(3)
public class MyAspect1 {

@Component
@Aspect
@Order(2)
public class MyAspect2 {

@Component
@Aspect
@Order(1)
public class MyAspect3 {

运行结果:
得到的运行顺序与上例不同,如图所示。
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值