Aop切面编程,动态代理

代理

后期会使用会在启动类中@MapperScan(“路径”)注解进行代替

数据库事务说明

案例分析:

  1. userMapper.insert(User对象)
  2. deptMapper.insert(dept对象)
    说明: 由于业务需求 要求方法要么同时入库,要么同时回滚.所以必须通过事务进行控制.

Spring实现事务控制(demo)

代码结构如下

*事物的控制应该写在service层;
Spring中规定:
* 如果传入的是接口的类型 则自动查找/注入 该接口的实现类, 条件必须:该接口只有一个实现类

public class TestUser {


    /**
     * Controller-Service-Mapper(Dao)
     * Spring中规定:
     *         如果传入的是接口的类型 则自动查找/注入 该接口的实现类
     *         该接口只有一个实现类
     * 注入接口的原理:
     *          if(getBean(isinterface)){
     *              Class targetClass = interface.getImpl();
     *              根据类型,动态获取对象
     *              return 对象
     *          }
     */
    @Test
    public void testTx(){
        ApplicationContext context =
                new AnnotationConfigApplicationContext(SpringConfig.class);
      
        // 向上造型
        //通过类型进行注入
        UserService userService = context.getBean(UserService.class);
        //通过名字进行注入加入的是一个实现类,切首字母小写;
        //UserService userService = (UserService) context.getBean("userServiceImpl");
        User user = new User();
        user.setId(101);
        user.setName("SpringAOP测试入门案例");
        userService.addUser(user);
    }

将数据库的事物控制写在service层但是如果事物都写在service那么就会出现代码冗余,所以使用代理模式;
在这里插入图片描述

代理模式

代理:用户表面上使用的是真实的对象实际上使用的是代理对象,在项目中的服务器中

作用:在不该变原有的方法的前提下对方法进行拓展

组成部分

1.要求代理者实现与被代理者相同的接口
2.在代理方法中实现功能的扩展
3.用户调用代理对象完成功能(用户认为代理就是目标对象)

具体的代码的实现

pom.xml的代码


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring_demo_7_tx</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <!--Spring核心包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入SpringBean-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入context包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入表达式jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入日志依赖-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>

        <!--引入测试包-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

</project>


配置类的代码

pojo的代码


package com.jt.pojo;

public class User {

    private Integer id;
    private String name;

    /*业务中实体对象不会交给Spring容器管理 所以需要添加方法*/

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

usermapper的接口


package com.jt.mapper;

import com.jt.pojo.User;

public interface UserMapper {

    void addUser(User user);

    void deleteUser(User user);
}


usermapper的接口实现类


package com.jt.mapper;

import com.jt.pojo.User;
import org.springframework.stereotype.Repository;

@Repository
public class UserMapperImpl implements UserMapper{

    //??事务控制应该在那一层完成!!!! dao/mapper service
    @Override
    public void addUser(User user) {

        System.out.println("用户入库:"+user);
    }

    @Override
    public void deleteUser(User user) {

        System.out.println("删除用户:"+user);
    }
}

service接口

package com.jt.service;

import com.jt.pojo.User;

public interface UserService {

    void addUser(User user);
    void deleteUser(User user);
}

service接口实现类


package com.jt.service;

import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("target")
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;

    //事务控制应该放到Service层中进行控制
    @Override
    public void addUser(User user) {
        userMapper.addUser(user);
    }

    @Override
    public void deleteUser(User user) {
        userMapper.deleteUser(user);
    }


    /*//事务控制应该放到Service层中进行控制
    @Override
    public void addUser(User user) {
        try {
            System.out.println("Spring事务开始");
            userMapper.addUser(user);
            System.out.println("事务结束");
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("事务回滚");
        }
    }*/

}

静态代理(只能实现一个接口)

通过代理实现事物的控制

角色划分:
1.目标对象target UserServiceImpl类
2.目标方法 method addUser()方法
3.代理: 实现事务控制.
4.代理对象与目标对象实现相同的接口.

静态代理弊端

1).静态代理只针对于某个接口 不能实现所有接口的代理 实用性较差

在这里插入图片描述

2).静态代理中所有的方法,都需要手动的添加事务开始/事务提交代码 代码冗余 不够简洁.

在这里插入图片描述

代理相当于一个接口有两个实现类,且这两个实现类还要进行来回调用(此时通过名字进行依赖注入)但是代理对象和真实的实现类不再一个包里;

代理类的名字为其名为userService
请添加图片描述

将真实的实现类起名字为target
在这里插入图片描述

代理类的代码


package com.jt.proxy;

import com.jt.pojo.User;
import com.jt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("userService")
public class StaticProxy implements UserService {

    //要求引入目标对象
    @Autowired //ByType  byName
    //@Qualifier("target")
    private UserService target;

    //目的: 对原有方法进行扩展
    @Override
    public void addUser(User user) {
        try {
            System.out.println("事务开始");
            //调用真实的目标方法
            target.addUser(user);
            System.out.println("事务结束");
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("事务回滚");
        }
    }

    @Override
    public void deleteUser(User user) {
        try {
            System.out.println("事务开始");
            //调用真实的目标方法
            target.deleteUser(user);
            System.out.println("事务结束");
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("事务回滚");
        }
    }

   /* public void updateUser(User user) {
        try {
            System.out.println("事务开始");
            //调用真实的目标方法
            target.updateUser(user);
            System.out.println("事务结束");
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("事务回滚");
        }
    }*/
}

测试类的代码

package com.jt.test;

import com.jt.config.SpringConfig;
import com.jt.pojo.User;
import com.jt.proxy.JDKProxyFactory;
import com.jt.service.UserService;
import com.jt.service.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestUser {

    @Test
    public void testStaticProxy(){
        ApplicationContext context =
                new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = (UserService) context.getBean("userService");
        User user = new User();
        user.setId(10001);
        user.setName("测试代理机制");
        //执行用户调用
        userService.addUser(user);
    }
}

动态代理

1.JDK代理:
要求: 要求目标对象必须实现接口
代理要求: 代理对象也必须实现目标对象的接口
目标对象/代理关系: 目标对象与代理对象(proxy)兄弟关系.

2.CGlib代理
要求: 不管目标对象是否有接口,都可以为其创建代理对象
代理要求: 要求代理对象必须继承目标对象
目标对象/代理关系: 目标对象与代理对象是父子关系

如果new 一个借口就要重写里面的方法
创建一个代理类

java api 的一个方法Proxy.newProxyInstance()

测试jdk动态代理
创建一个代理类


package com.jt.proxy;

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

//能否利用一个工厂动态为目标对象创建代理
public class JDKProxyFactory {

    //要求用户传递目标对象
    //关于匿名内部类用法说明jdk版本较低的时候需要加final:  匿名内部类引用外部参数 要求参数必须final修饰
    //object说明所有的对象都可以进行使用该代理
    public static Object getProxy(final Object target){
        //1.调用java API实现动态代理
        /**
         *  参数分析: 3个参数
         *      1.ClassLoader loader, 类加载器(获取目标对象的Class)
         *      2.类<?>[] interfaces,  JDK代理要求 必须有接口
         *                             java中可以多实现
         *      3.InvocationHandler h  对目标方法进行扩展
         */
        //1.获取类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //2.获取接口数组
        Class[] interfaces = target.getClass().getInterfaces();
        //3.通过动态代理创建对象
        //
        Object proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {

            //invoke方法: 代理对象调用方法时,invoke开始执行,扩展方法的编辑位置
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //proxy: 代理对象本身
                //method: 用户调用的方法对象
                //args:   用户调用方法的参数

                // result 标识目标方法执行的返回值
                Object result = null;
                try {
                    //添加事务的控制
                    System.out.println("事务开始");
                    //执行目标方法
                    // target真实的目标对象,method方法对象,args方法参数,invoke动态的执行目标方法;
                    result = method.invoke(target,args);
                    System.out.println("事务提交");
                }catch (Exception e){
                    e.printStackTrace();
                    System.out.println("事务回滚");
                }

                return result;
            }
        });

        return proxy;
    }

}

创建测试类

package com.jt.test;

import com.jt.config.SpringConfig;
import com.jt.pojo.User;
import com.jt.proxy.JDKProxyFactory;
import com.jt.service.UserService;
import com.jt.service.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestUser {
/**
     * 测试JDK动态代理
     */
    @Test
    public void testJDKProxy(){
        ApplicationContext context =
                new AnnotationConfigApplicationContext(SpringConfig.class);
        //1.获取用户目标对象
        UserService target = (UserService) context.getBean("target");
      //拿到的是目标对象实际上是代理对象;
        //2.获取代理对象
        UserService userService = (UserService) JDKProxyFactory.getProxy(target);
        //3.打印代理对象的类型
        System.out.println(userService.getClass());
        //4.用户完成调用
        User user = new User();
        user.setId(1001);
        user.setName("JDK动态代理完成");
        //新增用户方法
        userService.addUser(user);
        //动态代理对方法进行优化
        //删除用户的方法
        userService.deleteUser(user);
    }
}

JDK动态代理流程

在这里插入图片描述

动态代理优势

(代理的内容代码就是公共的代码,即无论写几个service代码代理的代码只需要写一个即可)
将公共的部分写到动态代理中,之后其他的业务类调用即可

编辑DeptService/DeptServiceImpl

1).编辑DeptService

在这里插入图片描述

2).编辑DeptServiceImpl
在这里插入图片描述

编辑测试类

public class TestDept {

    @Test
    public void testTx(){
        //1.获取目标对象
        ApplicationContext context =
                new AnnotationConfigApplicationContext(SpringConfig.class);
        DeptService target = (DeptService) context.getBean("deptService");
        //2.获取代理对象
        DeptService deptService = (DeptService) JDKProxyFactory.getProxy(target);
        //通过代理对象 调用方法  扩展了方法!!!!!
        deptService.addDept();  //invoke
    }
}

动态代理实现方案(二)

业务需求

要求对Service层的方法记录其执行的时间!!! 通过执行时间的长短 进行针对性的优化!!!
要求: service中 有 addUser方法/deleteUser方法.
要求代码结构扩展性好,耦合性低.
做完1000块!!!

编辑UserService/UserServiceImpl
1).编辑UserService


public interface UserService {

    void addUser(User user);
    void deleteUser(User user);
}

.编辑UserServiceImpl


@Service("target")
public class UserServiceImpl implements UserService{

    @Override
    public void addUser() {
        System.out.println("新增用户");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}



代理工厂

package com.jt.proxy;

import javax.annotation.PostConstruct;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKProxyFactory {

    //编辑静态方法获取代理对象
    public static Object getProxy(final Object target){
        //3个参数  1.类加载器  2.对象的接口
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class[] interfaces = target.getClass().getInterfaces();
        Object proxy = Proxy.newProxyInstance(classLoader, interfaces,
                new InvocationHandler() {
                    //代理对象执行目标方法时执行
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        //让用户执行目标方法
                        Long startTime = System.currentTimeMillis(); //开始时间
                        //执行目标方法 获取返回值 可能为null
                        Object result = method.invoke(target);
                        Long endTime = System.currentTimeMillis();  //结束时间
                        //根据项目经理要求 给程序预留bug 后期维护时删除 不友好
                        Thread.sleep(2000);
                        System.out.println("程序执行:"+(endTime-startTime)+"毫秒");
                        //将返回值传递给调用者
                        return result;
                    }
                })  ;

        return proxy;
    }


}

编辑测试案例


public class TestSpring {

    @Test
    public void test01(){
        ApplicationContext context =
                new AnnotationConfigApplicationContext(SpringConfig.class);
        //1.获取目标对象
        UserService target = (UserService) context.getBean("target");
        //2.获取代理对象
        UserService proxy = (UserService) JDKProxyFactory.getProxy(target);
        System.out.println(proxy.getClass());
        //3.调用业务方法
        proxy.addUser();
    }
}

AOP

底层是通过动态代理进行实现的即底层是动态代理;
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
总结: AOP(面向切面编程) 主要利用动态代理的模式 降低程序的耦合度,扩展业务功能方法.

什么样的方法需要放入到aop中

答:耦合性较高且又必须需要修改的方法放入到切面中来;

作用

在方法执行前后加功能,即对功能的方法都拓展。

组成

1.2 关于AOP名词介绍
1).连接点(Joinpoint): 用户可以被扩展的方法,但是现在还没有被扩展;

请添加图片描述

2).切入点PointCut: 用户实际扩展的方法这里的adduser就由原来 的连接点变成了切入点,找到指定包里的类,类里的方法,增加功能;
请添加图片描述

3).通知Advice: 扩展方法的具体实现,即就是要实现什么样的功能;
4).切面Aspect: 将通知应用到切入点的过程,其实就是一个类,由通知和切点组成;

1.3 通知类型(必会)

before: 在目标方法执行之前执行
afterReturning: 在目标方法执行之后返回时执行
afterThrowing: 在目标方法执行之后,抛出异常时执行
after: 无论程序是否执行成功,都要最后执行的通知(相当于关闭该方法 的资源)
around: 在目标方法执行前后 都要执行的通知(完美体现了动态代理模式),功能最为强大 只有环绕通知可以控制目标方法的执行

通知类型的注解要加下面的包
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.ProceedingJoinPoint;

关于通知方法总结:

1.环绕通知是处理业务的首选. 可以修改程序的执行轨迹
2.另外的四大通知一般用来做程序的监控.(监控系统) 只做记录

切入点表达式

概念:当程序满足切入点表达式,才能进入切面,执行通知方法.

1.bean(“bean的ID”) 根据beanId进行拦截 只能匹配一个
2.within(“包名.类名”) 可以使用通配符( * 或 ? ) 能匹配多个.
粒度: 上述的切入点表达式 粒度是类级别的. 粗粒度. 最细粒度的是方法的参数
3.execution(返回值类型 包名.类名.方法名(参数列表…));
粒度: 控制的是方法参数级别. 所以粒度较细. 最常用的.
4.@annotation(包名.注解名) 只拦截注解.
粒度: 注解是一种标记 根据规则标识某个方法/属性/类 细粒度

通知Advice:就是类里的一个方法,分为前置通知(方法执行前要加的),后置通知(方法执行后要加的),环绕通知(方法执行前后都要加的),返回后通知,异常通知

AOP入门案例

导入jar包

需求
统计所有service层的方法的执行时间

 <!--引入AOP包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--切面包  -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.6</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.6</version>
        </dependency>


pom.xml文件的全部代码


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring_demo_9_aop</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <!--Spring核心包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入SpringBean-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入context包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入表达式jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入日志依赖-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>

        <!--引入测试包-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <!--引入AOP包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--切面包  -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.6</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.6</version>
        </dependency>

        <!--当下所有的jar包都需要自动手动的依赖 并且需要确定依赖的关系,对初学者不友好-->
    </dependencies>
</project>

配置切面类

切面 = 切入点表达式 + 通知方法


//1.AOP需要被Spring容器管理
@Component
//2.标识该类为AOP切面
//  Spring容器默认不能识别切面注解,需要手动配置
@Aspect
public class SpringAOP {

    //1.定义before通知
    @Before("bean(deptServiceImpl)")
    public void before(){
        System.out.println("我是before通知");
    }
}

编辑配置类


@Configuration
@ComponentScan("com.jt")
@EnableAspectJAutoProxy(proxyTargetClass=false) //启动AOP注解 创建代理对象
                        //默认启用JDK动态代理,
                        //目标对象没有实现接口时,则采用CGLIB
                        //强制使用cglib proxyTargetClass=true
                        //JDK代理创建速度快.运行时稍慢
                        //CGLIB创建时速度较慢,运行时更快
public class SpringConfig {
}

编辑测试代码

在这里插入图片描述

关于表达式写法


package com.jt.aop;

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

//1.AOP需要被Spring容器管理
@Component
//2.标识该类为AOP切面
//  Spring容器默认不能识别切面注解,需要手动配置
@Aspect
public class SpringAOP {
    /**
     * 切入点表达式练习
     * within:
     *  1.within(com.jt.*.DeptServiceImpl)   一级包下的类
     *  2.within(com.jt..*.DeptServiceImpl)  ..代表多级包下的类
     *  3.within(com.jt..*)  包下的所有的类
     *
     * execution(返回值类型 包名.类名.方法名(参数列表))
     *  1.execution(* com.jt..*.DeptServiceImpl.add*())
     *  注释: 返回值类型任意的, com.jt下的所有包中的DeptServiceImpl的类
     *        的add开头的方法 ,并且没有参数.
     *
     *  2.execution(* com.jt..*.*(..))
     *  注释: 返回值类型任意,com.jt包下的所有包的所有类的所有方法 任意参数.
     *
     *  3.execution(int com.jt..*.*(int))
     *  4.execution(Integer com.jt..*.*(Integer))
     *  强调: 在Spring表达式中没有自动拆装箱功能! 注意参数类型
     *
     * @annotation(包名.注解名)
     *     @Before("@annotation(com.jt.anno.Cache)")
     *    只拦截特定注解的内容.
     */


    //1.定义before通知
    //@Before("bean(deptServiceImpl)")
    //@Before("within(com.jt..*)")
    //@Before("execution(* com.jt..*.DeptServiceImpl.add*())")
    @Before("@annotation(com.jt.anno.Cache)")
    public void before(){
        System.out.println("我是before通知");
    }
}

关于通知方法测试

通知方法

在这里插入图片描述

配置类

package com.jt.config;

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

@Configuration
@ComponentScan("com.jt")
@EnableAspectJAutoProxy(proxyTargetClass=false) //只有加了此代码aop切面才会有效
//启动AOP的注解 创建代理对象
                        //默认启用JDK动态代理,
                        //目标对象没有实现接口时,则采用CGLIB
                        //强制使用cglib代理 proxyTargetClass=true
                        //JDK代理创建速度快.运行时稍慢
                        //CGLIB创建时速度较慢,运行时更快
public class SpringConfig {
}

service的代码


package com.jt.service;

public interface DeptService {

    void addDept();
    void updateDept();
    //AOP中的测试方法
    String after(Integer id);
    //测试异常通知
    void afterThrow();
    //测试环绕通知执行
    void doAround();
    //测试执行的顺序
    void doOrder();
}

service 接口的实现类的代码


package com.jt.service;

import com.jt.anno.Cache;
import org.springframework.stereotype.Service;

@Service
public class DeptServiceImpl implements DeptService{

    @Override
    public void addDept() {
        System.out.println("添加部门信息");
    }

    @Override
    @Cache      //被注解标识
    public void updateDept() {
        System.out.println("更新部门");
    }

    @Override
    @Cache  //标识该方法需要执行切面
    public String after(Integer id) {

        return "Spring通知的测试";
    }

    //让该方法执行时 抛出异常
    @Override
    @Cache
    public void afterThrow() {
        System.out.println("用户执行目标方法");
        //手动抛出算数异常
        int a  = 1/0;
    }

    @Override
    @Cache  //标识执行AOP中的方法
    public void doAround() {
        System.out.println("实现用户数据的入库操作");
    }

    @Override
    @Cache
    public void doOrder() {
        System.out.println("测试程序执行的顺序");
    }
}
创建切面1

package com.jt.aop;

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

import java.util.Arrays;

//1.AOP需要被Spring容器管理
@Component
//2.标识该类为AOP切面
//  Spring容器默认不能识别切面注解,需要手动配置
//@Aspect
public class SpringAOP {

    //面向切面编程 = 切入点表达式(IF判断) + 通知方法


    /**
     * 切入点表达式练习
     * within:
     *  1.within(com.jt.*.DeptServiceImpl)   一级包下的类
     *  2.within(com.jt..*.DeptServiceImpl)  ..代表多级包下的类
     *  3.within(com.jt..*)  包下的所有的类
     *
     * execution(返回值类型 包名.类名.方法名(参数列表))
     *  1.execution(* com.jt..*.DeptServiceImpl.add*())
     *  注释: 返回值类型任意的, com.jt下的所有包中的DeptServiceImpl的类
     *        的add开头的方法 ,并且没有参数.
     *
     *  2.execution(* com.jt..*.*(..))
     *  注释: 返回值类型任意,com.jt包下的所有包的所有类的所有方法 任意参数.
     *
     *  3.execution(int com.jt..*.*(int))
     *  4.execution(Integer com.jt..*.*(Integer))
     * 是所有包下的所有包下 的所有类的方法只有int的参数
     *  强调: 在Spring表达式中没有自动拆装箱功能! 注意参数类型
     *
     * @annotation(包名.注解名)
     *     @Before("@annotation(com.jt.anno.Cache)")
     *    只拦截特定注解的内容.
     */


    //1.定义before通知
    //@Before("bean(deptServiceImpl)")
    //@Before("within(com.jt..*)")
    //@Before("execution(* com.jt..*.DeptServiceImpl.add*())")
    //@Before("@annotation(com.jt.anno.Cache)")

    //1.定义切入点表达式  if判断
    @Pointcut("@annotation(com.jt.anno.Cache)")
    public void pointcut(){

    }

    /*Spring为了AOP动态获取目标对象及方法中的数据,则通过joinPoint对象
    * 进行数据的传递.
    * getSignature : 方法签名  获取方法的参数
    * */
    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        System.out.println("获取目标对象的类型:"+joinPoint.getTarget().getClass());
        System.out.println("获取目标对象类名:"+joinPoint.getSignature().getDeclaringTypeName());
        System.out.println("获取目标对象方法名:"+joinPoint.getSignature().getName());
        System.out.println("获取方法参数:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("我是before通知");
    }

    /**
     * 记录方法的方法返回值!!!
     * pointcut:  关联的切入点表达式
     * returning: 将方法的返回值,通过形参result进行传递
     * @AfterReturning(pointcut = "pointcut()",returning = "result")
     * 注意事项:
     *      如果参数中需要添加joinPoint 对象时,参数必须位于第一位.
     *      Spring在进行参数赋值时,采用index[0] 下标的方式赋值
     * 报错提示: ::0xxxx
     */
    @AfterReturning(pointcut = "pointcut()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        System.out.println(Arrays.toString(joinPoint.getArgs()));
        System.out.println("用户的返回值结果:"+result);
        System.out.println("我是afterReturning通知");
    }

    /*
    * throwing = "e" 动态接收程序运行时的报错信息,
    * 利用异常通知进行记录
    * */
    @AfterThrowing(pointcut = "pointcut()",throwing = "e")
    public void afterThrowing(Exception e){
        System.out.println("获取异常信息:"+e.getMessage());
        System.out.println("获取异常的类型:"+e.getClass());
        System.out.println("我是afterThrowing");

    }

    @After("pointcut()")
    public void after(){
        System.out.println("我是after通知");
    }

    /**
     * 关于环绕通知的说明
     * 作用: 可以控制目标方法是否执行.
     * 参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
     * 注意事项:
     *      ProceedingJoinPoint 只能适用环绕通知
     * @return
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint){
        Object result = null;
        try {
            System.out.println("环绕通知开始");
            //1.执行下一个通知  2.执行目标方法 3.接收返回值
            Long start = System.currentTimeMillis();
            result = joinPoint.proceed();
            Long end = System.currentTimeMillis();
            System.out.println("耗时:"+(end-start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return result;
    }
}

关于的切入点表达式@annotation(包名.注解名) 只拦截注解的说明

此注解:在代码没有任何功能,只不过被 @annotation(com.jt.anno.Cache)进行拦截进行标示执行切面表达式的功能;


package com.jt.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//自定义注解   包括元注解
//注解的作用:  配合AOP进行注解类型 案例的训练   标识
//控制注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
//注解的作用对象  方法有效  类有效 TYPE
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
public @interface Cache {

}

请添加图片描述

请添加图片描述

关于@Before作用

说明: 前置通知,在目标方法执行之前执行
用途: 如果需要记录程序在方法执行前的状态,则使用前置通知.
需求: 1.获取目标对象的类型
2.获取目标方法的名称
3.获取目标方法的参数


/*Spring为了AOP动态获取目标对象及方法中的数据,则通过joinPoint对象
    * 进行数据的传递.
    * getSignature : 方法签名  获取方法的参数
    * */
    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        System.out.println("获取目标对象的类型:"+joinPoint.getTarget().getClass());
        System.out.println("获取目标对象类名:"+joinPoint.getSignature().getDeclaringTypeName());
        System.out.println("获取目标对象方法名:"+joinPoint.getSignature().getName());
        System.out.println("获取方法参数:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("我是before通知");
    }

@AfterReturning
说明: afterReturning,在目标方法执行之之后执行
用途: 用来监控方法的返回值,进行日志的记录

1).在接口中添加测试方法
在这里插入图片描述

实现目标方法

在这里插入图片描述

编辑AOP方法测试

在这里插入图片描述


 @AfterReturning(pointcut = "pointcut()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        System.out.println(Arrays.toString(joinPoint.getArgs()));
        System.out.println("用户的返回值结果:"+result);
        System.out.println("我是afterReturning通知");
    }

@AfterThrowing

作用: 当目标方法执行时,抛出异常时 可以使用AfterThrowing 进行记录.

编辑业务接口

在这里插入图片描述

编辑业务实现类
在这里插入图片描述

编辑异常通知类型
AfterThrowing里的throwing记录的是日志信息;相当于pointcut里的returning

在这里插入图片描述

@Around

@Around作用
规则: 在目标方法执行前后都要执行
实际作用: 可以控制目标方法是否执行.

环绕通知学习

ProceedingJoinPoint仅使用于around通知;
proceed()方法底层封装了invoke()的方法,所以执行的功能都是一样的,proceed()是ProceedingJoinPoint里的方法;


/**
     * 关于环绕通知的说明
     * 作用: 可以控制目标方法是否执行.
     * 参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
     * 注意事项:
     *      ProceedingJoinPoint is only supported for around advice
     * @return
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint){
        Object result = null;
        try {
            System.out.println("环绕通知开始");
            //1.执行下一个通知  2.执行目标方法 3.接收返回值
            Long start = System.currentTimeMillis();
            result = joinPoint.proceed();
            Long end = System.currentTimeMillis();
            System.out.println("耗时:"+(end-start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return result;
    }


关于Spring 中AOP流程介绍

1.8.1 AOP中的名词解释
1).连接点: 用户可以被扩展的方法 joinPoint
2).切入点: 用户实际扩展的方法(判断方法能否进入切面) pointcut 切入点表达式
3).通知: 扩展方法的具体实现 @before
4).切面: 将通知应用到切入点的过程 方法功能得到扩展全部配置(切面=切入点表达式+通知方法)

在这里插入图片描述

关于知识点讲解

pointcut
说明:判断方法能否进入切面 IF判断
注解说明: @Pointcut 内部编辑切入点表达式
判断依据:

在这里插入图片描述

joinPoint(连接点)

说明: 当用户执行方法时,如果方法满足pointcut表达式,则该方法就是连接点.
如果通知中需要获取方法相关参数 则Spring会将joinPoint对象进行封装.为通知参数进行赋值

在这里插入图片描述

通知方法的执行顺序

1.执行around开始
2.执行before
3.执行目标方法
4.执行afterReturning
5.执行afterThrowing
6.执行after
7,执行around通知结束

关于Order注解说明

说明: 研究如果是多个切面,如何控制切面的执行的顺序.
方法: 通过Order注解实现.
@order() 注解的作用就是当有多个切面时控制切面的 执行的顺序 , ()里 的值越小越先执行;
创建切面A


package com.jt.aop;

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

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

    @Before("@annotation(com.jt.anno.Cache)")
    public void before(){
        System.out.println("我是切面A执行");
    }
}

创建切面B


package com.jt.aop;

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

@Component
@Aspect
@Order(1)  //控制程序的执行顺序 通过数字进行控制  值越小 越先执行
public class Before2 {

    @Before("@annotation(com.jt.anno.Cache)")
    public void before(){
        System.out.println("我是切面B执行");
    }
}

在这里插入图片描述

在这里插入图片描述

创建切面 2

切面=切入点表达式+通知方法

package cn.tedu.service;
 
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;
//spring的aop功能,其实就是为了增强方法的功能,由切点和通知组成
//aop的使用场景:事务管理,缓存管理,权限管理,日志管理,性能测试
@Component
@Aspect//1. 标记是一个切面
public class AspectTest {
    //2. 切点表达式:用来给 指定包,类,方法 加功能
    //*代表一个值 ..表示多个值 第1个*表示方法的返回值 第2个*表示类 第2个*表示方法
    //@Pointcut("execution(返回值 包名.类名.方法名(参数列表) )")
    @Pointcut("execution(* cn.tedu.service..*.*(..) )")
    public void point(){}
    //3. 通知:本质就是一个方法,增强功能
    @Around("point()")//标志这是环绕通知,是在方法执行前后都加了功能
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis() ;
 
        Object o = joinPoint.proceed();//继续执行原来的业务方法
 
        long end = System.currentTimeMillis() ;
 
        System.out.println("aop计算的耗时是:"+(end-start));
 
        return o ; //返回这个结果
    }
}


测试

在这里插入图片描述

其实只有around是执行功能其他的通知都是在执行日志

1. 缓存控制(机制)

当第一次查询的时候从数据库中进行查询,第二次以后从缓存中进行查询;

业务说明

用户有一个缓存的集合 static Map<k:v> key=id号 value=User对象 根据id将User对象进行了缓存
要求:当用户第二次根据Id查询用户时,如果缓存中有数据,则直接返回!!!

业务分析

1).首选用AOP方式实现
2).通知方法: 使用环绕通知
3).切入点表达式: execution(…)

AOP切面实现

pojo类


package com.jt.pojo;

public class User {

    private Integer id;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

service.的接口的代码


package com.jt.service;

import com.jt.pojo.User;

public interface UserService {

    void findUser(User user);
    
}

service.的接口的实现类的代码


package com.jt.service;

import com.jt.pojo.User;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImplB implements UserService{
    @Override
    public void findUser(User user) {

        System.out.println("查询的结果是"+user);

    }


}

配置类


package com.jt.config;

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

@Configuration
@ComponentScan("com.jt")

public class SpringConfig {
}

测试类

请添加图片描述

切面的类


package com.jt.aop;

import com.jt.pojo.User;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
@Aspect
public class SpringAOP {

    private static Map<Integer,User> map = new HashMap();
    /**
     * 需求: 用户第一次查询走目标方法
     *       用户第二次查询走缓存  不执行目标方法
     * 判断依据: 如何判断用户是否为第一次查询?
     *       通过map集合进行判断 有数据 证明不是第一次查询
     * 执行步骤:
     *       1.获取用户查询的参数
     *       2.判断map集合中是否有该数据.
     *       true:  从map集合中get之后返回
     *       false: 执行目标方法,之后将user对象保存到Map中
     */

    //切入点表达式: 拦截service包中的所有方法
    @Around("execution(* com.jt.service..*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;
        //1.获取目标对象的参数
        Object[] args = joinPoint.getArgs();
        //2.强制类型转化为对象
        User user = (User) args[0];
        //3.判断map集合中是否有该数据  user的Id是唯一标识
        int id = user.getId();
        if(map.containsKey(id)){
            //map中有数据
            System.out.println("AOP缓存执行");
            //将获取的数据返回
            return map.get(id);
        }else{
            //map中没有数据 执行目标方法
            result = joinPoint.proceed();
            //将user对象保存到Map中
            map.put(id, user);
            System.out.println("AOP执行目标方法");
        }
        return result;
    }
}



版权声明:本文为weixin_58276266原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。