Spring3(代理模式 Spring1案例补充 Aop 面试题)

news2024/9/22 17:20:45

目录

一、代理模式

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

什么是代理模式?

为什么要用代理模式?

有哪几种代理模式?

1. 静态代理

实现

2. 基于接口的动态代理(jdk自带)

实现

3. 基于子类的动态代理 

实现 

二、Spring1案例补充

1. 常规实现

 2. 代理实现

三、AOP

xml实现日志记录

注解实现日志记录

四、面试题


一、代理模式

        在代理模式(Proxy Pattern)中,一个类代表另一个类的功能,这种类型的设计模式属于结构型模式。

        代理模式通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介,负责将客户端的请求转发给目标对象,同时可以在转发请求前后进行额外的处理。

        在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

介绍

意图

为其他对象提供一种代理以控制对这个对象的访问。

主要解决的问题

  • 代理模式解决的是在直接访问某些对象时可能遇到的问题,例如对象创建成本高、需要安全控制或远程访问等。

使用场景

  • 当需要在访问一个对象时进行一些控制或额外处理时。

实现方式

  • 增加中间层:创建一个代理类,作为真实对象的中间层。
  • 代理与真实对象组合:代理类持有真实对象的引用,并在访问时进行控制。

关键代码

  • 代理类:实现与真实对象相同的接口,并添加额外的控制逻辑。
  • 真实对象:实际执行任务的对象。

应用实例

  • 快捷方式:Windows系统中的快捷方式作为文件或程序的代理。
  • 角色扮演:孙悟空作为高翠兰的代理,猪八戒无法区分。
  • 代售点:购买火车票时,代售点作为火车站的代理。
  • 支票:作为银行账户资金的代理,控制资金的访问。
  • Spring AOP:使用代理模式来实现面向切面编程。

优点

  • 职责分离:代理模式将访问控制与业务逻辑分离。
  • 扩展性:可以灵活地添加额外的功能或控制。
  • 智能化:可以智能地处理访问请求,如延迟加载、缓存等。

缺点

  • 性能开销:增加了代理层可能会影响请求的处理速度。
  • 实现复杂性:某些类型的代理模式实现起来可能较为复杂。

使用建议

  • 根据具体需求选择合适的代理类型,如远程代理、虚拟代理、保护代理等。
  • 确保代理类与真实对象接口一致,以便客户端透明地使用代理。

注意事项

  • 与适配器模式的区别:适配器模式改变接口,而代理模式不改变接口。
  • 与装饰器模式的区别:装饰器模式用于增强功能,代理模式用于控制访问。

结构

主要涉及到以下几个核心角色:

  • 抽象主题(Subject):

    • 定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题。
  • 真实主题(Real Subject):

    • 实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问。
  • 代理(Proxy):

    • 实现了抽象主题接口,并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能,例如延迟加载、权限控制、日志记录等。
  • 客户端(Client):

    • 使用抽象主题接口来操作真实主题或代理主题,不需要知道具体是哪一个实现类。

什么是代理模式?

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
通俗的来讲代理模式就是我们生活中常见的中介。
举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了

为什么要用代理模式?

中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,们还可以通过给代理
类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。 

有哪几种代理模式?

 我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话
可以分为两种:
静态代理:
        静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。
动态代理:
动态代理是在程序运行时通过反射机制动态创建的。
动态代理分为:

  • 基于接口的动态代理(jdk自带)
  • 基于子类的动态代理(第三方) 

1. 静态代理

实现

 创建一个接口。

public interface IWomen {
    public void  makeEyesWithMan();//抛媚眼
    public void  happyWithMan();//开心
}

创建实现接口的实体类

public class PanJinLianImp implements IWomen{
    public void makeEyesWithMan() {
        System.out.println("=======回眸一笑,抛个媚眼======");
    }

    public void happyWithMan() {
        System.out.println("=========嘿嘿嘿,老娘来了~~~==========");
    }
}

代理

//代理 中间商
public class WangPoImp implements IWomen{

    //被代理
    IWomen iWomen;

    public WangPoImp() {
    }

    public WangPoImp(IWomen iWomen) {
        this.iWomen = iWomen;
    }

    public void makeEyesWithMan() {
        System.out.println("=======斟一壶酒,搞搞气氛======");
        iWomen.makeEyesWithMan();
    }

    public void happyWithMan() {
        iWomen.happyWithMan();
    }
}

请求

public class XiMenQing {
    public static void main(String[] args) {
        //1.目标(被代理对象)
        PanJinLianImp panJinLianImp = new PanJinLianImp();
        //2.代理
        WangPoImp wangPoImp = new WangPoImp(panJinLianImp);

        wangPoImp.makeEyesWithMan();
        wangPoImp.happyWithMan();
    }
}

2. 基于接口的动态代理(jdk自带)

基于接口的动态代理:

  • 特点:字节码随用随创建,随用随加载
  • 作用:不修改源码的基础上对方法增强

涉及的类:Proxy
提供者:JDK官方
如何创建代理对象:
          使用Proxy类中的newProxyInstance方法
创建代理对象的要求:
         被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法的参数:

  • ClassLoader:类加载器
  •          它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
  •  Class[]:字节码数组
  •          它是用于让代理对象和被代理对象有相同方法。固定写法。
  • InvocationHandler:用于提供增强的代码

实现

接口

public interface ISinger {
    public void sing();
    public void rap();
}

实现类

public class CaiXuKunImp implements ISinger{
    public void sing() {
        System.out.println("========鸡你太美========");
    }

    public void rap() {
        System.out.println("=======rap=========");
    }
}

 测试

public class Test01 {
    public static void main(String[] args) {
        //1.被代理对象
        final CaiXuKunImp caiXuKunImp = new CaiXuKunImp();
        /**
         * 基于接口的动态代理:
         *  特点:字节码随用随创建,随用随加载
         *  作用:不修改源码的基础上对方法增强
         *  涉及的类:Proxy
         *  提供者:JDK官方
         *  如何创建代理对象:
         *      使用Proxy类中的newProxyInstance方法
         *  创建代理对象的要求:
         *      被代理类最少实现一个接口,如果没有则不能使用
         *  newProxyInstance方法的参数:
         *      ClassLoader:类加载器
         *          它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
         *      Class[]:字节码数组
         *          它是用于让代理对象和被代理对象有相同方法。固定写法。
         *      InvocationHandler:用于提供增强的代码
         *
         */


        ISinger jinjinren = (ISinger) Proxy.newProxyInstance(caiXuKunImp.getClass().getClassLoader(), caiXuKunImp.getClass().getInterfaces(), new InvocationHandler() {

            /***
             *  Object proxy=====》被代理对象的引用===(蔡徐坤对象)
             *  Method method====》执行的方法========(蔡徐坤唱歌方法)
             *  Object[] args====》执行方法的参数=====(蔡徐坤唱歌方法的参数)
             *  Object===========》执行方法的返回值===(蔡徐坤唱歌方法的返回值)
             * */
            /**
             * 作用:执行被代理对象的任何接口方法都会经过该方法
             * 方法参数的含义
             *  proxy   代理对象的引用
             *  method  当前执行的方法
             *  args    当前执行方法所需的参数
             *  Object  和被代理对象方法有相同的返回值
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                System.out.println("跳一段舞");
                Object obj = method.invoke(caiXuKunImp, args);//代表调用被代理对象的核心方法
                System.out.println("打一个篮球");

                return obj;
            }
        });
        //3.唱歌
        jinjinren.sing();
        System.out.println("***********************");
        jinjinren.rap();
    }
}

 

3. 基于子类的动态代理 

基于子类的动态代理

  • 涉及的类:Enhancer
  • 提供者:第三方cglib库
  • 开发环境:添加cglib依赖坐标

如何创建代理对象:
        使用Enhancer类中的create方法
创建代理对象的要求:
        被代理类不能是最终类
create方法的参数:

  • Class:字节码
    • 它是用于指定被代理对象的字节码。
  •  Callback:用于提供增强的代码
    •   它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。

此接口的实现类都是谁用谁写。
我们一般写的都是该接口的子接口实现类:MethodInterceptor

实现 

注入

   <dependencies>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1_3</version>
        </dependency>
    </dependencies>

接口

public interface ISinger {
    public void sing();
}
public class ZhouShenImp implements ISinger{
    @Override
    public void sing() {
        System.out.println("========大鱼海棠========");
    }
}
public class Test01 {
    public static void main(String[] args) {
        1.被代理对象
        final ISinger zhouShenImp = new ZhouShenImp();

        ISinger jingjiren = (ISinger) Enhancer.create(zhouShenImp.getClass(), new InvocationHandler() {
            /**
             * 2.创建代理对象
             * 参数1:被代理对象的字节码
             * 参数2:InvocationHandler
             * */
            /**
             * 执行被代理对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param methodProxy :当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                System.out.println("====找一下蔡徐坤=====");
                Object obj =  method.invoke(zhouShenImp,objects);
                System.out.println("======和蔡徐坤一起基太美=====");
                return obj;
            }
        });

        //3.唱歌
        jingjiren.sing();
    }
}

二、Spring1案例补充

1. 常规实现

在Spring1中的基础上 实现 转钱功能

xml实现

项目状态:成功提交,失败不能做到一个业务方法一起回滚
分析原因:项目存在事务自动管理,且自动管理在dao层实现
解决方案:事务管理未来一定是在service层实现
方案思考:
    1.dao层不在进行事务管理,自动事务提交关闭
    2.业务类的每个业务方法中的多个dao操作,公用同一个连接connection对象
    3.ThreadLocal

dao层不变

service

接口新增

public void transfer(String sourceName,String targetName,int money);

实现类 重写该方法

@Override
    public void transfer(String sourceName, String targetName, int money) {
        try {
            //开启事务
            transactionUtil.beginTx();
            Account sourceAccount = dao.findByName(sourceName);
            Account targetAccount = dao.findByName(targetName);

            sourceAccount.setAmoney(sourceAccount.getAmoney()-money);
            targetAccount.setAmoney(targetAccount.getAmoney()+money);

            dao.updateById(sourceAccount);
            int a =10/0;//模拟异常
            dao.updateById(targetAccount);
            //提交事务
            transactionUtil.commitTx();
        } catch (Exception e) {
            //回滚事务
            transactionUtil.rollbackTx();
            e.printStackTrace();
        } finally {
            //关闭事务
            transactionUtil.closeTx();
        }
    }

controller同理

 public void transfer(String sourceName,String targetName,int money);

@Override
    public void transfer(String sourceName, String targetName, int money) {
        service.transfer(sourceName,targetName,money);
    }

util        

ConnectionUtil
public class ConnectionUtil {
    //线程区域对象
    ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();

    //数据源
    DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    //获取连接
    public Connection createConn() {
        try {
            //在口袋里面找连接
            Connection connection = connectionThreadLocal.get();
            //判断
            if (connection==null){
                connection=dataSource.getConnection();
                connectionThreadLocal.set(connection);
            }
            return  connection;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return  null;
    }
    //关闭连接
    public void closeConn(){
        connectionThreadLocal.remove();//解绑
    }
}

TransactionUtil

public class TransactionUtil {
    //专配连接工具类
    ConnectionUtil connectionUtil;

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    //开启
    public void beginTx() {
        try {
            connectionUtil.createConn().setAutoCommit(false);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    //提交
    public void commitTx() {
        try {
            connectionUtil.createConn().commit();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    //回滚
    public void rollbackTx() {
        try {
            connectionUtil.createConn().rollback();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    //关闭
    public void closeTx() {
        try {
            connectionUtil.createConn().close();
            connectionUtil.closeConn();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

同时需要在service 实现类装配事务工具类

//装配事务工具类
    TransactionUtil transactionUtil;

    public void setTransactionUtil(TransactionUtil transactionUtil) {
        this.transactionUtil = transactionUtil;
    }

dao层装配连接工具类

 //装配连接工具类
    ConnectionUtil connectionUtil;

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

在xml文件中注入

<!-- 注入连接工具类 -->
    <bean id="connectionUtil" class="com.zkt.util.ConnectionUtil">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 注入事务工具类 -->
    <bean id="transactionUtil" class="com.zkt.util.TransactionUtil">
        <property name="connectionUtil" ref="connectionUtil"/>
    </bean>
    <!-- 注入dao -->
    <bean id="dao" class="com.zkt.dao.AccountDaoImp">
        <property name="queryRunner" ref="queryRunner"/>
        <property name="connectionUtil" ref="connectionUtil"/>
    </bean>
    <!-- 注入service -->
    <bean id="service" class="com.zkt.service.AccountServiceImp">
        <property name="dao" ref="dao"/>
        <property name="transactionUtil" ref="transactionUtil"/>
    </bean>

 2. 代理实现

在util下创建代理

public class ProxyFactory {
    //被代理对象装配
    IAccountService toServiceProxy;

    public void setToServiceProxy(IAccountService toServiceProxy) {
        this.toServiceProxy = toServiceProxy;
    }
    //装配事务
    TransactionUtil transactionUtil;

    public void setTransactionUtil(TransactionUtil transactionUtil) {
        this.transactionUtil = transactionUtil;
    }

    //创建代理
    public IAccountService createProxy(){
        IAccountService service = (IAccountService) Proxy.newProxyInstance(toServiceProxy.getClass().getClassLoader(), toServiceProxy.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object obj = null;
                try {
                    transactionUtil.beginTx();
                    obj = method.invoke(toServiceProxy, args);
                    transactionUtil.commitTx();
                } catch (Exception e) {
                    e.printStackTrace();
                    transactionUtil.rollbackTx();
                } finally {
                    transactionUtil.closeTx();
                }
                return obj;
            }
        });
        return  service;
    }
}

事务工具类不变

连接工具类

public class ConnectionUtil {
    //线程区域对象
    ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();

    //数据源
    DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    //获取连接
    public Connection createConn() {
        try {
            //在口袋里面找连接
            Connection connection = connectionThreadLocal.get();
            //判断
            if (connection==null){
                connection=dataSource.getConnection();
                connectionThreadLocal.set(connection);
            }
            return  connection;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return  null;
    }
    //关闭连接
    public void closeConn(){
        connectionThreadLocal.remove();//解绑
    }
}

故service的transfer 正常写就行

 @Override
    public void transfer(String sourceName, String targetName, int money) {


        Account sourceAccount = dao.findByName(sourceName);
        Account targetAccount = dao.findByName(targetName);

        sourceAccount.setAmoney(sourceAccount.getAmoney() - money);
        targetAccount.setAmoney(targetAccount.getAmoney() + money);

        dao.updateById(sourceAccount);
//        int a = 10 / 0;//模拟异常
        dao.updateById(targetAccount);

    }

最后 在xml文件中 注入就行

  <!-- 注入service(被代理对象) -->
    <bean id="service" class="com.zkt.service.AccountServiceImp">
        <property name="dao" ref="dao"/>
    </bean>
    <!-- 注入service(代理对象) -->
    <bean id="proxyService" class="com.zkt.service.AccountServiceImp" factory-bean="factory" factory-method="createProxy"/>
    <bean id="factory" class="com.zkt.util.ProxyFactory">
        <property name="transactionUtil" ref="transactionUtil"/>
        <property name="toServiceProxy" ref="service"/>
    </bean>
    <!-- 注入controller(消费者) -->
    <bean id="controller" class="com.zkt.controller.AccountControllerImp">
        <property name="service" ref="proxyService"/>
    </bean>

其余不变 即可实现

三、AOP

        AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术

        AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,
是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得
业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP 的作用及其优势

  •  作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
  •  优势:减少重复代码,提高开发效率,并且便于维护

AOP 的应用

  • 1.日志
  • 2.事务
  • 3.权限


Spring AOP 基于动态代理实现:

  • 如果被代理的对象,已经实现某个接口,则 Spring AOP 会使用 JDK Proxy(反射),基于接口的方式,创建代理对象(JDK动态代理的核心是InvocationHandler接口和Proxy类);
  • 如果被代理的对象,没有实现某个接口,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP 会使用 Cglib,基于继承的方式,生成一个被代理对象的子类来作为代理(Cglib动态代理的核心是MethodInterceptor接口和Enhancer类);
     

AOP术语:

AOP通知类型
        AOP将抽取出来的共性功能称为通知;通知类型:以通知在上下文中的具体位置作为划分

  •         前置通知(Before)
  •         后置通知(After)
  •         返回通知(After-returning)
  •         异常通知(After-throwing)
  •         环绕通知(Around)

AOP连接点(Join point)
        AOP将所有的方法都视为连接点,不管是接口里面的抽象方法,还是实现类里面的重写方法,都是连接点

AOP切点(Pointcut)
        AOP将可能被抽取共性功能的方法称为切入点。切入点是连接点的子集

AOP目标对象(Target):

        就是挖掉功能的方法对应的类生的对象,这种对象是无法直接完成最终工作的

AOP织入(Weaving):

        就是将挖掉的功能回填的动态过程

AOP切面:

        切点+通知


实现步骤:

  • 1.添加依赖,aop与aspectj表达式的依赖
  • 2.创建spring的主配置文件,bean内的命名空间要添加aop的
  • 3.创建业务代码并编写日志记录代码(事务管理代码)
  • 4.将业务层与日志记录层注入spring容器
  • 5.<aop:config>--aop配置
  •         aop:aspect--aop切面
  •                aop:before--通知内容与通知类型

切点表达式配置语法:
 

execution(修饰符 返回值 包名称.类名称.方法名称(参数列表))

eg:
    execution(public void com.apesource.service.ServiceImp.findAll())
1.修饰符可以省略代表任意
    execution(返回值 包名称.类名称.方法名称(参数列表))
2.返回值可以使用“*”代表任意
    execution(* 包名称.类名称.方法名称(参数列表))
3.包名可以使用“*”代表任意名称
    execution(* *.*.*.类名称.方法名称(参数列表))
4.包名可以使用“..”代表任意个数
    execution(* *...类名称.方法名称(参数列表))
5.类名与方法名可以使用“*”代表任意
    execution(* *...*.*(参数列表))
6.参数列表可以使用".."代表任意个数任意类型
    execution(* *...*.*(..))
    如果有参数
        int======>int
        String===>java.lang.String

xml实现日志记录

poi.xml 导入坐标

<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>

service

public interface IAccountService {
    
    public void save(int i);
    
    public void update();

    public int delete();
}

public class AccountServiceImp implements IAccountService {
    public void save(int i) {
        System.out.println("业务层的新增方法:" + i);
    }

    public void update() {
        System.out.println("业务层的修改方法");
//        int a = 10/0;
    }

    public int delete() {
        System.out.println("业务层的删除方法");
        return 0;
    }
}

util

public class Logger {
    public void beforeMethod() {
        System.out.println("日志类logger===前置通知");
    }

    public void returnMethod() {
        System.out.println("日志类logger===返回通知");
    }

    public void throwMethod() {
        System.out.println("日志类logger===异常通知");
    }


    public void afterMethod() {
        System.out.println("日志类logger===后置通知");
    }
}

applicationContext.xml

 <!-- 注入业务层 -->
    <bean id="accountServiceImp" class="com.zkt.service.AccountServiceImp"/>
    <!-- 注入日志记录层(通知) -->
    <bean id="logger" class="com.zkt.util.Logger"/>
    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切面  -->
        <aop:aspect id="aopAspect" ref="logger">
            <!-- 切点 -->
            <aop:pointcut id="dian" expression="execution(* com.zkt.service.AccountServiceImp.* (..))"/>
            <!-- 通知 -->
            <aop:before method="beforeMethod" pointcut-ref="dian"/>
            <aop:after-returning method="returnMethod" pointcut-ref="dian"/>
            <aop:after-throwing method="throwMethod" pointcut-ref="dian"/>
            <aop:after method="afterMethod" pointcut-ref="dian"/>
        </aop:aspect>
    </aop:config>

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Test01 {
    @Autowired
    public IAccountService service;

    @Test
    public void show1(){
        service.update();
//        System.out.println("===================");
//        service.save(1);
//        System.out.println("===================");
//        service.delete();
    }
}

注解实现日志记录

导入坐标同理

加入注解

util使用环绕通知实现

@Component
@Aspect//切面
public class Logger {

    @Pointcut("execution(* com.zkt.service.AccountServiceImp.* (..))")
    public void dian() {

    }
//    @Before("dian()")
//    public void beforeMethod() {
//        System.out.println("日志类logger===前置通知");
//    }
//
//    @AfterReturning("dian()")
//    public void returnMethod() {
//        System.out.println("日志类logger===返回通知");
//    }
//
//    @AfterThrowing("dian()")
//    public void throwMethod() {
//        System.out.println("日志类logger===异常通知");
//    }
//
//    @After("dian()")
//    public void afterMethod() {
//        System.out.println("日志类logger===后置通知");
//    }

    //环绕通知
    @Around("dian()")
    public Object AroundLogger(ProceedingJoinPoint pjp) {
        Object returnobj = null;//保存主业务方法的返回值
        try {
            //1.前置通知
            System.out.println("环绕通知===》前置通知");

            Object[] objs = pjp.getArgs();//主业务方法的参数
            returnobj = pjp.proceed(objs);//调用主业务方法


            //3.后置通知
            System.out.println("环绕通知===》返回通知");
        } catch (Throwable tw) {
            //4.异常通知
            System.out.println("环绕通知===》异常通知");
        } finally {
            //5.最终通知
            System.out.println("环绕通知===》后置通知");
        }
        return returnobj;

    }
}

测试 异常情况

正常情况

四、面试题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1932804.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于python旅游景点满意度分析设计与实现

1.1研究背景与意义 1.1.1研究背景 随着旅游业的快速发展&#xff0c;满意度分析成为评估旅游景点质量和提升游客体验的重要手段。海口市作为中国的旅游城市之一&#xff0c;其旅游景点吸引了大量游客。然而&#xff0c;如何科学评估和提升海口市旅游景点的满意度&#xff0c;…

Qt创建列表,通过外部按钮控制列表的选中下移、上移以及左侧图标的显现

引言 项目中需要使用列表QListWidget,但是不能直接拿来使用。需要创建一个列表,通过向上和向下的按钮来向上或者向下移动选中列表项,当当前项背选中再去点击确认按钮,会在列表项的前面出现一个图标。 实现效果 本实例实现的效果如下: 实现思路 思路一 直接采用QLis…

Spring Security之安全异常处理

前言 在我们的安全框架中&#xff0c;不管是什么框架&#xff08;包括通过过滤器自定义&#xff09;都需要处理涉及安全相关的异常&#xff0c;例如&#xff1a;登录失败要跳转到登录页&#xff0c;访问权限不足要返回页面亦或是json。接下来&#xff0c;我们就看看Spring Sec…

海外营销推广:快速创建维基百科(wiki)词条-大舍传媒

一、维基百科的永久留存问题 许多企业和个人关心维基百科是否能永久留存。实际上&#xff0c;只要企业和个人的行为没有引起维基百科管理方的反感&#xff0c;词条就可以长期保存。如果有恶意行为或被投诉&#xff0c;维基百科可能会对词条进行删除或修改。 二、创建维基百科…

为fooocus v2.5.0安装groundingdino

在win10下折就fooocus&#xff0c;使用git pull命令更新本地&#xff0c;然后…\python_embeded\python.exe -m pip install -r .\requirements_versions.txt更新依赖关系包。 卡在groundingdino的安装上&#xff0c;先在requirements_versions.txt中删除它&#xff0c;安装其他…

第十课:telnet(远程登入)

如何远程管理网络设备&#xff1f; 只要保证PC和路由器的ip是互通的&#xff0c;那么PC就可以远程管理路由器&#xff08;用telnet技术管理&#xff09;。 我们搭建一个下面这样的简单的拓扑图进行介绍 首先我们点击云&#xff0c;把云打开&#xff0c;点击增加 我们绑定vmn…

线程的中断和同步问题

1、自动终断【完成】&#xff1a;一个线程完成执行后&#xff08;即run方法执行完毕&#xff09;&#xff0c;不能再次运行 。 2、手动中断&#xff1a; stop( ) —— 已过时&#xff0c;基本不用。&#xff08;不安全&#xff0c;就像是突然停电&#xff09; interrupt( ) …

VTK----3D picking的原理、类型及实现

目录 3D picking概述 3D射线投射原理 VTK picking框架 vtkPicker(选Actor) vtkPointPicker(选点) vtkCellPicker(选单元) vtkAreaPicker(框选) 3D picking概述 3D picking 是一种在三维场景中确定用户点击或指向的对象的技术。这在3D应用程序和游戏中非常常见,…

CentOS 7 初始化环境配置详细

推荐使用xshell远程连接&#xff0c;如链接不上 请查看 CentOS 7 网络配置 修改主机名 hostname hostnamectl set-hostname xxx bash 关闭 SElinux 重启之后生效 配置yum源&#xff08;阿里&#xff09; 先备份CentOS-Base.repo&#xff0c;然后再下载 mv /etc/yum.repos…

MySQL学习记录 —— 이십이 MySQL服务器日志

文章目录 1、日志介绍2、一般、慢查询日志1、一般查询日志2、慢查询日志FILE格式TABLE格式 3、错误日志4、二进制日志5、日志维护 1、日志介绍 中继服务器的数据来源于集群中的主服务。每次做一些操作时&#xff0c;把操作保存到重做日志&#xff0c;这样崩溃时就可以从重做日志…

STM32(六):STM32指南者-定时器实验

目录 一、基本概念1、常规定时器2、内核定时器 二、基本定时器实验1、实验说明2、编程过程&#xff08;1&#xff09;配置LED&#xff08;2&#xff09;配置定时器&#xff08;3&#xff09;设定中断事件&#xff08;4&#xff09;主函数计数 3、工程代码 三、通用定时器实验实…

高数知识补充----矩阵、行列式、数学符号

矩阵计算 参考链接&#xff1a;矩阵如何运算&#xff1f;——线性代数_矩阵计算-CSDN博客 行列式计算 参考链接&#xff1a;实用的行列式计算方法 —— 线性代数&#xff08;det&#xff09;_det线性代数-CSDN博客 参考链接&#xff1a;行列式的计算方法(含四种&#xff0c;…

基于 asp.net家庭财务管理系统设计与实现

博主介绍&#xff1a;专注于Java .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用感兴趣的可以先…

破解反爬虫策略 /_guard/auto.js(二)实战

这次我们用上篇文章讲到的方法来真正破解一下反爬虫策略&#xff0c;这两个案例是两个不同的网站&#xff0c;一个用的是 /_guard/auto.js&#xff0c;另一个用的是/_guard/delay_jump.js。经过解析发现这两个网站用的反爬虫策略基本是一模一样&#xff0c;只不过在js混淆和生成…

k8s核心操作_存储抽象_K8S中使用Secret功能来存储密码_使用免密拉取镜像_k8s核心实战总结---分布式云原生部署架构搭建033

注意在看的时候一定要把 dxxxx中的xxxx换成--o----c----k----e----r 然后我们再来看一个k8s中的secret的功能,这个功能 用来存储密码的,configMap是用来存配置的 比如我们有个pod,他的镜像,如果是需要密码的,那么 我们现在是从公共仓库拉取的,如果我们从私有仓库拉取,有密码…

搜维尔科技:【研究】触觉技术将在5年内以8种方式改变人们的世界

触觉技术在过去几年中发展迅猛&#xff0c;大大提高了反馈的精确度和真实度。其应用产生了真正的影响&#xff0c;数百家公司和企业都集成了触觉技术来增强培训和研究模拟。 虽然触觉技术主要用于 B2B 层面&#xff0c;但触觉技术可能会彻底改变我们的生活&#xff0c;尤其是通…

《梦醒蝶飞:释放Excel函数与公式的力量》12.4 DMAX函数

第12章&#xff1a;数据库函数 第四节 12.4 DMAX函数 12.4.1 简介 DMAX函数是Excel中的一个数据库函数&#xff0c;用于返回数据库或数据表中特定条件下某字段的最大值。DMAX函数在处理大规模数据、数据筛选和分析时非常有用。 12.4.2 语法 DMAX(database, field, criteri…

Keka for Mac v1.4.3 中文下载 解压/压缩工具

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功 三、运行测试1、打开软件2、文件访问权限修改3、访达扩展 安装完成&#xff01;&#xff…

安全防御:智能选路

目录 一、智能选路 1.1 就近选路 1.2 策略路由 1.3 虚拟系统---VRF 二、全局选路策略 1&#xff0c;基于链路带宽进行负载分担 2&#xff0c;基于链路质量进行负载分担 3&#xff0c;基于链路权重的负载分担 4&#xff0c;根据链路优先级的主备备份 DNS透明代理 一、…

【MySQL篇】Percona XtraBackup工具备份指南:常用备份命令详解与实践(第二篇,总共五篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…