Spring-----AOP面向切面编程

Spring两大核心

1.IOC【DI】:控制反转,把创建对象和管理对象交于spring容器

2.AOP:面向切面编程

1.AOP面向切面编程

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP:把核心业务代码和非核心业务代码分离,如果我们写的核心业务代码需要非核心业务功能时,在不影响核心业务代码的前期下,可以添加进去。

Service层:

package com.wzh;


public interface MathService {

    /**
     * 加法运算
     * @param a
     * @param b
     * @return
     */
    public double add(double a,double b);

    /**
     * 减法运算
     * @param a
     * @param b
     * @return
     */
    public double sub(double a,double b);

    /**
     * 乘法运算
     * @param a
     * @param b
     * @return
     */
    public double mul(double a,double b);

    /**
     * 除法运算
     * @param a
     * @param b
     * @return
     */
    public double div(double a,double b);
}

Service实现类:

package com.wzh;

/**
 * 思考: 随着业务的不断扩展:
 *   ① 日志功能。如果日志代码修改: 以AAA开始 感觉:如果有1w方法都需要添加日志,修改1W方法的代码
 *   ② 校验功能:如果1w方法需要校验,修改1w方法的源代码。
 *
 *  有没有好的解决方案:
 *   (1)抽取个方法。缺点还需要再1w方法的地方调用。
 *   (2)动态代理: 实现方式有两种: [1]JDK原生动态代理 [2]cglib动态代理
 *   (3)AOP面向切面编程:AOP的底层实现就是基于动态代理。
 **/
public class MathServiceImpl implements MathService {
    public double add(double a, double b) {
        System.out.println("AAA--->The add method begin with ["+a+","+b+"]");
        double result=a+b;
        System.out.println("AAA--->The add method end with "+result);
        return result;
    }

    public double sub(double a, double b) {
        System.out.println("AAA--->The sub method begin with ["+a+","+b+"]");
        double result=a-b;
        System.out.println("AAA--->The sub method end with "+result);
        return result;
    }

    public double mul(double a, double b) {
        System.out.println("AAA--->The mul method begin with ["+a+","+b+"]");
        double result=a*b;
        System.out.println("AAA--->he mul method end with "+result);
        return result;
    }

    public double div(double a, double b) {
        System.out.println("AAA--->The div method begin with ["+a+","+b+"]");
        double result=a/b;
        System.out.println("AAA--->The div method end with "+result);
        return result;
    }
}

 * 思考: 随着业务的不断扩展:
 *   ① 日志功能。如果日志代码修改: 以AAA开始 感觉:如果有1w方法都需要添加日志,修改1W方法的代码
 *   ② 校验功能:如果1w方法需要校验,修改1w方法的源代码。
 *
 *  有没有好的解决方案:
 *   (1)抽取个方法。缺点还需要再1w方法的地方调用。
 *   (2)动态代理: 实现方式有两种: [1]JDK原生动态代理 [2]cglib动态代理
 *   (3)AOP面向切面编程:AOP的底层实现就是基于动态代理。用到了JDK和cglib两种动态代理,

它会根据有没有接口调用响应的动态代理

1.1.JDK原生动态代理

Spring-----AOP面向切面编程_第1张图片

MathService接口:

package com.jdk;

public interface MathService {

    /**
     * 加法运算
     * @param a
     * @param b
     * @return
     */
    public double add(double a,double b);

    /**
     * 减法运算
     * @param a
     * @param b
     * @return
     */
    public double sub(double a,double b);

    /**
     * 乘法运算
     * @param a
     * @param b
     * @return
     */
    public double mul(double a,double b);

    /**
     * 除法运算
     * @param a
     * @param b
     * @return
     */
    public double div(double a,double b);
}

MathServiceImpl实现类:

package com.jdk;

/**
 * 思考: 随着业务的不断扩展:
 *   ① 日志功能。如果日志代码修改: 以AAA开始 感觉:如果有1w方法都需要添加日志,修改1W方法的代码
 *   ② 校验功能:如果1w方法需要校验,修改1w方法的源代码。
 *
 *  有没有好的解决方案:
 *   (1)抽取个方法。缺点还需要再1w方法的地方调用。
 *   (2)动态代理: 实现方式有两种: [1]JDK原生动态代理 [2]cglib动态代理
 *   (3)AOP面向切面编程:AOP的底层实现就是基于动态代理。
 **/
public class MathServiceImpl implements MathService {
    public double add(double a, double b) {
        double result=a+b;
        return result;
    }

    public double sub(double a, double b) {
        double result=a-b;
        return result;
    }

    public double mul(double a, double b) {
        double result=a*b;
        return result;
    }

    public double div(double a, double b) {
        double result=a/b;
        return result;
    }
}

代理工厂类ProxyFactory

method:表示代理对象要代理的方法
invoke:回调该方法
args:方法需要的参数

类加载器:

Spring-----AOP面向切面编程_第2张图片

package com.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * @ProjectName: AOP0706
 * @Package: com.jdk
 * @ClassName: ProxyFactory
 * @Author: 王振华
 * @Description: 代理工厂类
 * @Date: 2022/7/6 15:00
 * @Version: 1.0
 */
//代理工厂类理解为:经纪公司
public class ProxyFactory {
    //被代理的对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //获取代理对象
    public Object getProxy(){
        /** 三个参数
         * ClassLoader loader, 被代理对象需要的类加载器
         * Class[] interfaces, 被代理对象实现的接口
         * InvocationHandler h  调用被代理对象的方法时,会触发该对象的invoke方法,回调函数
         */
        ClassLoader loader = target.getClass().getClassLoader();;
        Class[] interfaces = target.getClass().getInterfaces();
        //匿名对象
        InvocationHandler h = new InvocationHandler() {
            //当调用被代理对象的方法时,执行该方法
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //可以加入你需要的业务代码
                //method.getName():得到方法名
                //args:参数
                System.out.println("AAA--->The "+method.getName()+" method begin with "+ Arrays.asList(args));
                //method:表示代理对象要代理的方法
                //invoke:回调该方法
                //args:方法需要的参数
                Object result = method.invoke(target, args);
                System.out.println("AAA--->The "+method.getName()+" method end with "+result);
                return result;
            }
        };
        //在JDK中提供了一个代理Proxy  通过该类获取代理对象
        Object o = Proxy.newProxyInstance(loader,interfaces,h);
        return o;
    }
}

测试代码

package com.jdk;

public class Test {
    public static void main(String[] args) {
        //被代理对象
        MathService target=new MathServiceImpl();
        //创建代理工厂对象
        ProxyFactory proxyFactory = new ProxyFactory(target);
        //获取代理对象
        MathService proxy = (MathService) proxyFactory.getProxy();
        //执行相应的业务代码
        double result = proxy.add(10, 5);
        System.out.println("结果:"+result);
    }
}

Spring-----AOP面向切面编程_第3张图片

 JDK原生动态代理的缺点

必须基于接口实现

1.2.cglib

Spring-----AOP面向切面编程_第4张图片

1.引入cglib的jar包

        
            cglib
            cglib
            3.2.5
        

MathServiceImpl类:

package com.cglib;

/**
 * 思考: 随着业务的不断扩展:
 *   ① 日志功能。如果日志代码修改: 以AAA开始 感觉:如果有1w方法都需要添加日志,修改1W方法的代码
 *   ② 校验功能:如果1w方法需要校验,修改1w方法的源代码。
 *
 *  有没有好的解决方案:
 *   (1)抽取个方法。缺点还需要再1w方法的地方调用。
 *   (2)动态代理: 实现方式有两种: [1]JDK原生动态代理---缺点:必须基于接口实现 [2]cglib动态代理----可以不用实现接口
 *   (3)AOP面向切面编程:AOP的底层实现就是基于动态代理。
 **/
public class MathServiceImpl{
    public double add(double a, double b) {
        double result=a+b;
        return result;
    }

    public double sub(double a, double b) {
        double result=a-b;
        return result;
    }

    public double mul(double a, double b) {
        double result=a*b;
        return result;
    }

    public double div(double a, double b) {
        double result=a/b;
        return result;
    }
}

2.创建代理工厂类并实现 MethodInterceptor接口

ProxyFactory类:

package com.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @ProjectName: AOP0706
 * @Package: com.cglib
 * @ClassName: ProxyFactory
 * @Author: 王振华
 * @Description: 代理工厂类
 * @Date: 2022/7/7 12:20
 * @Version: 1.0
 */
public class ProxyFactory implements MethodInterceptor {

    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //获取代理对象,给目标对象创建一个代理对象
    public Object getProxy(){
        //1.工具类
        Enhancer enhancer = new Enhancer();
        //2.指定被代理对象的父类
        enhancer.setSuperclass(target.getClass());  //我们只需要修改这行代码
        //3.指定回调类(回调函数)
        enhancer.setCallback(this);
        //4.创建子类(代理对象)
        return  enhancer.create();
    }

    //当代理对象执行代理方法时触发的方法
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("AAA--->The "+method.getName()+" method begin with "+ Arrays.asList(args));
        //method:表示代理对象要代理的方法
        //invoke:回调该方法
        //args:方法需要的参数
        Object result = method.invoke(target, args);
        System.out.println("AAA--->The "+method.getName()+" method end with "+result);
        return result;
    }
}

 测试代码:

package com.cglib;

import com.cglib.ProxyFactory;
import com.cglib.MathServiceImpl;

public class Test {
    public static void main(String[] args) {
        //被代理对象
        MathServiceImpl target=new MathServiceImpl();
        //创建代理工厂对象
        ProxyFactory proxyFactory = new ProxyFactory(target);
        //获取代理对象
        MathServiceImpl proxy = (MathServiceImpl) proxyFactory.getProxy();
        //执行相应的业务代码
        double result = proxy.add(10, 5);
        System.out.println("结果:"+result);
    }
}

优点:可以不用基于接口完成

2.AOP的实现

应用: 日志:----增加删除修改操作---->记录到日志表中.

JDK动态代理和cglib动态代理代码比较麻烦,----->springaop就是在动态代理的基础上进行了封装,就可以使用一些注解完成。

1.引入aspects依赖

        
            org.springframework
            spring-webmvc
            5.3.19
        
        
        
            org.springframework
            spring-aspects
            5.2.9.RELEASE
        

2.spring配置文件spring.xml



    
    
    
    

MathService接口

package com.wzh.aop;

public interface MathService {

    /**
     * 加法运算
     * @param a
     * @param b
     * @return
     */
    public double add(double a,double b);

    /**
     * 减法运算
     * @param a
     * @param b
     * @return
     */
    public double sub(double a,double b);

    /**
     * 乘法运算
     * @param a
     * @param b
     * @return
     */
    public double mul(double a,double b);

    /**
     * 除法运算
     * @param a
     * @param b
     * @return
     */
    public double div(double a,double b);
}

MathServiceImpl实现类

package com.wzh.aop;

import org.springframework.stereotype.Service;

/**
 * 思考: 随着业务的不断扩展:
 *   ① 日志功能。如果日志代码修改: 以AAA开始 感觉:如果有1w方法都需要添加日志,修改1W方法的代码
 *   ② 校验功能:如果1w方法需要校验,修改1w方法的源代码。
 *
 *  有没有好的解决方案:
 *   (1)抽取个方法。缺点还需要再1w方法的地方调用。
 *   (2)动态代理: 实现方式有两种: [1]JDK原生动态代理 [2]cglib动态代理
 *   (3)AOP面向切面编程:AOP的底层实现就是基于动态代理。
 **/
@Service //当包扫描到该注解时,spring会创建该类的对象
public class MathServiceImpl implements MathService {
    public double add(double a, double b) {
        double result=a+b;
        return result;
    }

    public double sub(double a, double b) {
        double result=a-b;
        return result;
    }

    public double mul(double a, double b) {
        double result=a*b;
        return result;
    }

    public double div(double a, double b) {
        double result=a/b;
        return result;
    }
}

3.创建切面类

package com.wzh.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @ProjectName: AOP0706
 * @Package: com.wzh.aop
 * @ClassName: LogAspect
 * @Author: 王振华
 * @Description:
 * @Date: 2022/7/7 12:52
 * @Version: 1.0
 */
@Component  //等价于@Service
@Aspect  //表示该类为切面类
public class LogAspect {
    @Before("execution(public double com.wzh.aop.MathServiceImpl.add(double,double))")
    public void before(){
        System.out.println("方法执行前的日志");
    }
    //总会被执行,不管有没有异常,在return之后执行
    @After("execution(public double com.wzh.aop.MathServiceImpl.add(double,double))")
    public void after(){
        System.out.println("方法执行后的日志");
    }
}

@Aspect @Before等注解Spring默认不识别  需要在spring.xml中加入

 
   

上面是指定路径下的具体类的具体方法执行,

如果想让任意返回类型,aop包下的所有类的所有方法都有切面日志  使用通配符

Spring-----AOP面向切面编程_第5张图片

Spring-----AOP面向切面编程_第6张图片

 其他注解

package com.wzh.aop;

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

import java.util.Arrays;

/**
 * @ProjectName: AOP0706
 * @Package: com.wzh.aop
 * @ClassName: LogAspect
 * @Author: 王振华
 * @Description:
 * @Date: 2022/7/7 12:52
 * @Version: 1.0
 */
@Component  //等价于@Service
@Aspect  //表示该类为切面类
public class LogAspect {
    @Before("execution(* com.wzh.aop.*.*(..))")
    public void before(JoinPoint joinPoint){

        //获得执行方法的名称
        String name = joinPoint.getSignature().getName();

        //获取执行方法的参数
        Object[] args = joinPoint.getArgs();
        //如果参数是对象
        //User user = (User)args[0]
        System.out.println("AAA--->The "+name+" method begin with "+ Arrays.asList(args));
    }
    //任意返回类型  aop包下的所有类都有切面日志  使用通配符
    //第一个*:修饰符和返回值类型
    //第二个*:所有类
    //第三个*:所有方法
    //一般加载业务层:service

    //总会被执行,不管有没有异常,等价于finally
    @After("execution(* com.wzh.aop.*.*(..))")
    public void after(){
        System.out.println("方法执行后的日志");
    }

    //返回通知
    @AfterReturning("execution(public double com.wzh.aop.MathServiceImpl.add(double,double))")
    public void afterReturning(){   //只有碰到return才会被执行
        System.out.println("碰到return关键字后执行");
    }

    //异常通知
    @AfterThrowing("execution(* com.wzh.aop.*.*(..))")
    public void afterThrowing(){
        System.out.println("出现异常了");
    }
}

@Before:方法执行前

@After:不管有没有异常都会执行,相当于finally

@AfterReturning:碰到return关键字执行

@AfterThrowing:碰到异常执行

测试

package com.wzh.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @program: qy151-aop
 * @description:
 * @author: 闫克起2
 * @create: 2022-07-06 14:11
 **/
public class Test {
    public static void main(String[] args) {
        //从spring容器中获取
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        MathService mathService = (MathService) applicationContext.getBean("mathServiceImpl");
        double result = mathService.add(10, 5);
        System.out.println("结果:"+result);
    }
}

aop就是在JDK动态代理和cglib动态代理的基础上进行了封装,它会根据类是否实现接口调用不同的动态代理

3.使用AOP完成登录日志

Spring-----AOP面向切面编程_第7张图片

 

1.引入依赖

Spring-----AOP面向切面编程_第8张图片

 2.创建切面类

package com.wzh.aop;

import com.wzh.entity.LoginLog;
import com.wzh.service.LoginLogService;
import com.wzh.service.StaffService;
import com.wzh.utils.CommonResult;
import com.wzh.vo.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * @ProjectName: computer
 * @Package: com.wzh.aop
 * @ClassName: LoginLogAop
 * @Author: 王振华
 * @Description: 登录日志
 * @Date: 2022/7/6 19:16
 * @Version: 1.0
 */
@Component
@Aspect
public class LoginLogAop {
    @Autowired
    private LoginLogService loginLogService;
    //returning 是方法返回的结果   
    //com.wzh.controller.StaffController.login(..)  登陆方法的路径
    @AfterReturning(value = "execution(* com.wzh.controller.StaffController.login(..))",returning = "result")
    public void LoginAfterReturn(JoinPoint joinPoint,Object result){
        //获取参数
        Object[] args = joinPoint.getArgs();
        User user = (User) args[0]; //数组里面只有user对象

        //获取request对象。
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        //获取登录者ip
        String ip = request.getRemoteAddr();

        //把结果强转为CommonResult类
        CommonResult commonResult = (CommonResult) result;
        //状态 0成功 1失败
        Integer status = -1;
        if(commonResult.getCode()==2000){
            status = 0;
        }else {
            status = 1;
        }

        //封装一个登录日志对象
        LoginLog loginLog = new LoginLog(null,user.getUsername(),ip,status,commonResult.getMsg(),new Date());

        loginLogService.save(loginLog);

    }
}

Spring-----AOP面向切面编程_第9张图片

 3.spring.xml配置文件

Spring-----AOP面向切面编程_第10张图片

 4.web.xml配置文件

Spring-----AOP面向切面编程_第11张图片

 

 

你可能感兴趣的:(spring框架,spring,java,后端)