ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Day5 Spring AOP

2019-12-25 19:01:13  阅读:265  来源: 互联网

标签:invoked aop Spring void Day5 method AOP userService public


Spring AOP

AOP 相关术语

Spring AOP是对方法的增强。

  • 连接点(JoinPoint)
    方法

  • 切入点(Pointcut)
    确定哪些方法需要增强

  • 通知(Advice)
    方法运行的哪一个阶段,开始时、结束时、返回时、异常时、开始+结束时?
    做了什么增强,添加日志、事务、负载均衡?

  • 切面(Aspect)
    在哪里做了哪些增强,即切面=切入点+通知

面向切面编程要做什么?
在哪些类的哪些方法,在方法运行到什么时候,做哪些增强;

在哪些类的哪些方法 = 切入点(Pointcut)
在方法运行到什么时候 + 做哪些增强 = 通知(Advice)

使用方法

基于接口

Spring AOP最早完全是几个接口来完成的。

advice

Spring AOP最早完全是几个接口来完成的。

  1. 通过实现AfterReturningAdvice接口,定义一个后置通知(被代理方法运行结束后,该通知被调用);
public class LogAdvice implements AfterReturningAdvice {

    private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(dateFormat.format(new Date()) + ": method " + method.getName() + " invoked.");
    }
}
  1. 配置xml文件
    重点是配置代理工厂:ProxyFactoryBean
    <!--被代理bean-->
    <bean id="userService" class="com.bailiban.day4.aop.dynamic_proxy.noproxy.UserServiceImpl" />

    <!--  代理通知bean  -->
    <bean id="logAdvice" class="com.bailiban.day4.aop.spring12.LogAdvice" />

    <!--  代理工厂bean  -->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--        被代理接口-->
        <property name="proxyInterfaces">
            <list>
                <value>com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService</value>
            </list>
        </property>
<!--        被代理bean-->
        <property name="target" ref="userService"/>
<!--        通知-->
        <property name="interceptorNames">
            <list>
                <value>logAdvice</value>
            </list>
        </property>
    </bean>

从userServiceProxy的配置可以看出,其配置与手动编写基于JDK的动态代理很类似,都设计三个方面:

  • 被代理接口:UserService
  • 被代理类:UserServiceImpl
  • 增强方法:logAdvice

测试:

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "com/bailiban/day4/aop/spring12/advice.xml");
        UserService userService = (UserService) context.getBean("userServiceProxy");
        userService.getUser();
        userService.createUser();
        userService.updateUser();
        userService.deleteUser();
    }

结果与手动编写动态代理代码一致:

getUser
2019-12-22 14:50:20: method getUser invoked.
createUser
2019-12-22 14:50:20: method createUser invoked.
updateUser
2019-12-22 14:50:20: method updateUser invoked.
deleteUser
2019-12-22 14:50:20: method deleteUser invoked.

基于接口还可以有其他更复杂的配置,来提供更丰富的AOP编程。不过现在很少使用基于接口来编写Spring AOP,在此略过。
其他用法可参考代码:https://github.com/Jun67/spring-demo/tree/master/src/main/java/com/bailiban/day4/aop/spring12

advisor

advice默认对指定代理对象的所有方法做增强,使用advisor可以进一步指定增强哪些方法。

  1. 新增一个advisor bean
<!--    在匹配(按方法名)的方法上通知-->
    <bean id="advisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<!--        1. 通知做哪些事情-->
        <property name="advice" ref="logAdvice" />
<!--        2. 指定哪些方法-->
        <property name="mappedNames" value="getUser" />
    </bean>
  1. 修改ProxyFactoryBean拦截器配置,添加advisor
<!--    动态代理-->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--        指定接口-->
        <property name="proxyInterfaces" value="com.bailiban.day5.aop.noproxy.UserService" />
<!--        指定代理对象-->
        <property name="target" ref="userService" />
<!--        指定增强的方法 -->
        <property name="interceptorNames">
            <list>
<!--                <value>beforeAdvice</value>-->
<!--                <value>logAdvice</value>-->
<!--                加入新增的advisor,用于控制指定方法做增强处理-->
                <value>advisor</value>
            </list>
        </property>
    </bean>
  1. 测试
    public static void main(String[] args) {

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "com/bailiban/day5/aop/springaop/bean/advisor/advisor.xml");
        UserService userService = (UserService) context.getBean("userServiceProxy");
        userService.createUser();
        userService.deleteUser();
        userService.updateUser();
        userService.getUser();
    }
  1. 运行结果
创建了一个新用户。
删除一个用户。
更新一个用户。
查找一个用户。
2019-12-25 09:02:55: method getUser invoked.
autoProxyCreator

advice和advisor,在使用代理bean的时候,需要通过代理bean(userServiceProxy)来获取原bean名称(userService)。

可以是autoProxyCreator来自定生成代理类。在使用时,直接访问原bean名称(userService)即可获取代理对象。

  1. 创建了一个新的代理bean
    <!--    动态代理,自动生成代理对象-->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!--       按bean的名称来进行代理,以Service结尾的bean都会被代理-->
        <property name="beanNames" value="*Service" />
        <!--        指定增强的方法 -->
        <property name="interceptorNames">
            <list>
                <!--   advisor,用于控制指定方法做增强处理-->
                <value>advisor</value>
            </list>
        </property>
    </bean>
  1. 测试
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "com/bailiban/day4/aop/spring12/autoproxy/autoproxy.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.getUser();
        userService.createUser();
        userService.updateUser();
        userService.deleteUser();
    }

结果:

getUser
2019-12-25 17:55:43: method getUser invoked.
createUser
2019-12-25 17:55:43: method createUser invoked.
updateUser
deleteUser
DafaultAdvisor..

能不能不配置代理bean的属性?即不指定哪些方法做了什么增强?

将userServiceProxy替换为一个DefaultAdvisorAutoProxyCreator类型的bean即可

<!--    动态代理,自动生成代理对象-->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
基于aop标签

Spring从2.0开始,提供了标签和@Aspectj注解两种AOP编程。

需要在pom.xml中添加如下依赖:

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
简单示例

使用示例:

    <!--被代理bean-->
    <bean id="userService" class="com.bailiban.day4.aop.dynamic_proxy.noproxy.UserServiceImpl" />

    <!--  代理通知bean  -->
    <bean id="logAdvice" class="com.bailiban.day4.aop.aopxml.LogAdvice" />
<!--    环绕切口-->
    <bean id="aroundAdvice" class="com.bailiban.day4.aop.aopxml.AroundAdvice" />

<!--    aop相关配置-->
    <aop:config>
<!--        定义切面,该切面的增强方法在logAdvice中定义的-->
        <aop:aspect ref="logAdvice">
<!--            指定在哪些类的哪些方法代理
        execution(* com.bailiban.day5.aop.noproxy.UserServiceImpl.*(..))
        execution:指定方法签名;
        *:表示任意字符,第一个* 表示方法的限定符(private,public),此处表示任意;
        com.bailiban.day5.aop.noproxy.UserServiceImpl:指定类名;
        第二个* :表示任意方法,即UserServiceImpl类下面的所有方法;
        (..):指定参数,此处使用..表示任意参数。注意".."也可以用来表示任意子包。
-->          
            <aop:pointcut id="log" expression="execution(* com..noproxy.UserServiceImpl.*(..))"/>
<!--            指定在代理方法运行结束之后,执行增强方法-->          
            <aop:after method="logger" pointcut-ref="log" />
        </aop:aspect>
      
<!--        定义切面,该切面的增强方法在aroundAdvice中定义的-->
        <aop:aspect ref="aroundAdvice">
            <aop:pointcut id="arround" expression="execution(* com..noproxy.UserServiceImpl.*(..))"/>
            <aop:around method="around" pointcut-ref="arround" />
        </aop:aspect>
    </aop:config>

关键点:

  1. <aop:config>:表明这是一个aop配置;
  2. <aop:aspect ref="logAdvice">
    配置切面,ref为该切面使用的通知(即增强代码)
  3. <aop:pointcut>:配置切入点
    重要:expression="execution(* com..noproxy.UserServiceImpl.*(..))"
    指定在哪里进行切入,即哪些方法会被增强。上面表达式表示UserServiceImpl类的所有方法都会被增强。
  4. <aop:around>:指定切入的时机,这里是环绕,即被代理的方法前后都会被增强。
    其他的切入点:
  • <aop:before>:方法运行之前;
  • <aop:after>:方法运行结束,但还没有返回值;
  • <aop:after-returning>:方法运行结束,且可以获得返回值;
  • <aop:after-throwing>:在异常中切入;

测试:

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "com/bailiban/day4/aop/aopxml/aop.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.getUser();
        userService.createUser();
        userService.updateUser();
        userService.deleteUser();
    }

运行结果:

开始执行...
getUser
结束执行...
2019-12-23 21:58:14: method getUser invoked.
开始执行...
createUser
结束执行...
2019-12-23 21:58:14: method createUser invoked.
开始执行...
updateUser
结束执行...
2019-12-23 21:58:14: method updateUser invoked.
开始执行...
deleteUser
结束执行...
2019-12-23 21:58:14: method deleteUser invoked.
示例:事务处理

手写事务

  1. 编写一个查询功能;
package com.bailiban.day5.aop.springaop.aop.transfer;

import java.sql.*;

public class JdbcUtil {

    private static String jdbcUrl = "jdbc:mysql://192.168.1.132:3306/spring_demo?serverTimezone=Asia/Shanghai";
    private static String username = "root";
    private static String password = "root";
    private static String driver = "com.mysql.jdbc.Driver";

    public static void main(String[] args) {

        // 1. Class.forName,加载用于mysql的驱动程序
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // Java7 支持try-with-resource语法,只要在try语句里面创建的资源,会自动关闭。
        try (
           // 2. 建立数据库连接
           Connection conn = DriverManager.getConnection(jdbcUrl, username, password);
           // 3. 创建statement
           Statement statement = conn.createStatement();
           // 4. 查询语句,并返回结果
           ResultSet rs = statement.executeQuery("select id, name, money, role from account");
        ) {
            // 5. 遍历查询结果
            while (rs.next()) {
                System.out.println(rs.getInt(1) + "\t" +
                        rs.getString(2) + "\t" +
                        rs.getDouble(3) + "\t" +
                        rs.getString(4));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  1. 编写非事务的转账功能
    public void transfer(int fromId, int toId, double money) throws SQLException {
        Account fromAccount = accountDao.findById(fromId);
        Account toAccount = accountDao.findById(toId);
        fromAccount.setMoney(fromAccount.getMoney() - money);
        accountDao.update(fromAccount);
//        此处可以手动抛出一个异常,此时钱被转走,但接收者并不会收到。
//        if (true)
//            throw new RuntimeException();
        toAccount.setMoney(toAccount.getMoney() + money);
        accountDao.update(toAccount);
    }

当没有事务管理时,程序功能可能出现异常。例如,上述最后一句accountDao.update(toAccount);执行失败(即接收者没有收到钱),而此时accountDao.update(fromAccount);以及执行成功了。就会出现钱减少,但没到账的问题。

  1. 编写支持事务的转账代码
    public void transfer2(int fromId, int toId, double money) {
        Account fromAccount = accountDao.findById(fromId);
        Account toAccount = accountDao.findById(toId);

        try {
            System.out.println("begin");
            JdbcUtil.connection.setAutoCommit(false);
            fromAccount.setMoney(fromAccount.getMoney() - money);
            accountDao.update(fromAccount);
//            if (true)
//                throw new RuntimeException();
            toAccount.setMoney(toAccount.getMoney() + money);
            accountDao.update(toAccount);
            System.out.println("commit");
            JdbcUtil.connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                System.out.println("rollback");
                JdbcUtil.connection.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }

关键点:

  • 开启事务
    JdbcUtil.conn.setAutoCommit(false);
  • 提交事务
    JdbcUtil.conn.commit();
  • 事务失败时,回滚事务
    JdbcUtil.conn.rollback();

    JdbcUtil 定义见:https://github.com/Jun67/spring-demo/blob/master/src/main/java/com/bailiban/day4/aop/aopxml/transaction/JdbcUtil.java

使用AOP写事务

  1. 添加四个拦截方法
  • 开始事务
    // 开始事务,用于 before advice
    public void transaction_begin() throws SQLException {
        System.out.println("begin");
        connection.setAutoCommit(false);
    }
  • 提交事务
    // 提交事务,用于 after-returning advice
    public void transaction_commit() throws SQLException {
        System.out.println("commit");
        connection.commit();
    }
  • 回滚事务
    // 回滚事务,用于 after-throwing advice
    public void transaction_rollback() throws SQLException {
        System.out.println("rollback");
        connection.rollback();
    }
  • 回收资源
    // 回收资源,此处并没有关闭连接,对应于 after advice
    public void transaction_release() {
        System.out.println("release");
    }
  1. 将拦截器配置到transfer方法上
<!--    事务处理相关方法-->
    <bean id="jdbcUtil" class="com.bailiban.day4.aop.aopxml.transaction.JdbcUtil" />

<!--    事务AOP配置-->
    <aop:config>
        <aop:aspect ref="jdbcUtil">
<!--            拦截transfer方法-->
            <aop:pointcut id="pc1" expression="execution(* com..transaction.*.transfer(..))"/>
<!--            transfer运行前,开启事务-->
            <aop:before method="transaction_begin" pointcut-ref="pc1" />
<!--            transfer运行结束后,提交事务-->
            <aop:after-returning method="transaction_commit" pointcut-ref="pc1" />
<!--            异常回滚-->
            <aop:after-throwing method="transaction_rollback" pointcut-ref="pc1" />
<!--            即使出现异常,after方法也会执行,模拟连接资源回收-->
            <aop:after method="transaction_release" pointcut-ref="pc1" />
        </aop:aspect>
    </aop:config>

标签:invoked,aop,Spring,void,Day5,method,AOP,userService,public
来源: https://www.cnblogs.com/cheng18/p/12098417.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有