• Java SSM入门(七)——Spring业务层


    iwehdio的博客园:https://www.cnblogs.com/iwehdio/

    1、事务管理

    • 同一个事务应该由同一个连接(connection)完成,需要使用ThreadLocal对象把连接与当前线程绑定,从而使一个线程中只有一个连接可以操作。

    • 事务控制应该在业务层。

    • 连接的工具类 ConnectionUtils,从数据源获取一个连接并且和线程绑定:

      public class ConnectionUtils {
      
          private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
      
          private DataSource dataSource;
      
          public void setDataSource(DataSource dataSource) {
              this.dataSource = dataSource;
          }
      
          public Connection getThreadConnection() {
              try {
                  //1、从当前线程获取连接
                  Connection connection = tl.get();
                  //2、判断当前线程是否有连接
                  if(connection == null) {
                      //3、没有则获取一个新连接
                      connection = dataSource.getConnection();
                      tl.set(connection);
                  }
                  //4、返回连接
                  return connection;
              } catch (SQLException e) {
                  throw new RuntimeException(e);
              }
          }
      
          public void removeConnection() {
              tl.remove();
          }
      }
      
    • 事务管理的工具类 TransactionManager,包括开启、提交、回滚事务和提交连接:

      public class TransactionManager {
      
          private ConnectionUtils connectionUtils;
      
          public void setConnectionUtils(ConnectionUtils connectionUtils) {
              this.connectionUtils = connectionUtils;
          }
      
          public void beginTransaction () {
              try {
                  connectionUtils.getThreadConnection().setAutoCommit(false);
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
          public void commit () {
              try {
                  connectionUtils.getThreadConnection().commit();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
          public void rollback () {
              try {
                  connectionUtils.getThreadConnection().rollback();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
          public void release () {
              try {
                  connectionUtils.getThreadConnection().close();
                  connectionUtils.removeConnection();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      }
      
    • 在 AccountDaoImpl 中,每次都使用与线程绑定的连接进行操作:

      public class AccountDaoImpl implements AccountDao {
      
          private QueryRunner runner;
          private ConnectionUtils connectionUtils;
      
          public void setConnectionUtils(ConnectionUtils connectionUtils) {
              this.connectionUtils = connectionUtils;
          }
      
          public void setRunner(QueryRunner runner) {
              this.runner = runner;
          }
          
          public void updateAccount(Account account) {
              try {
                  runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      }
      
    • 在 AccountServiceImpl 中,调用事务:

      public class AccountServiceImpl implements AccountService {
      
          private AccountDao accountDao;
          private TransactionManager txManager;
      
          public void setAccountDao(AccountDao accountDao) {
              this.accountDao = accountDao;
          }
      
          public void setTxManager(TransactionManager txManager) {
              this.txManager = txManager;
          }
          
           public void transfer(String sourceName, String targetName, float money) {
              try {
                  txManager.beginTransaction();
                  Account source = accountDao.findAccountByName(sourceName);
                  Account target = accountDao.findAccountByName(targetName);
                  source.setMoney(source.getMoney()-money);
                  target.setMoney(target.getMoney()+money);
                  accountDao.updateAccount(source);
                  accountDao.updateAccount(target);
                  System.out.println("提交");
                  txManager.commit();
      
              } catch (Exception e) {
                  txManager.rollback();
                  System.out.println("回滚");
                  e.printStackTrace();
              } finally {
                  txManager.release();
                  System.out.println("释放");
              }
          }
      }
      
    • 在 bean.xml 中设置注入:

      <beans>
      	<bean id="connectionUtils" class="cn.iwehdio.utils.ConnectionUtils">
              <property name="dataSource" ref="datasource"></property>
          </bean>
      
          <bean id="txManager" class="cn.iwehdio.utils.TransactionManager">
              <property name="connectionUtils" ref="connectionUtils"></property>
          </bean>
      
          <bean id="accountService" class="cn.iwehdio.service.Impl.AccountServiceImpl">
              <property name="accountDao" ref="accountDao"></property>
              <property name="txManager" ref="txManager"></property>
          </bean>
      
          <bean id="accountDao" class="cn.iwehdio.dao.Impl.AccountDaoImpl">
              <property name="runner" ref="runner"></property>
              <property name="connectionUtils" ref="connectionUtils"></property>
          </bean>
      </beans>
      

    2、基于代理的事务

    • 动态代理:

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

      • 分类:

        • 基于接口的动态代理。
      • 基于子类的动态代理。

      • 基于接口的动态代理:

      • 涉及的类:Proxy。

        • 提供者:JDK官方。
    • 如何创建基于接口的代理对象:

      • 使用Proxy类中的newProxyInstance方法。

      • 被代理类最少实现一个方法。

        • 参数:
        • ClassLoader:用于加载代理对象字节码,写的是被代理对象的类加载器。
          • .getClass().getClassLoader()
          • CLass[]:字节码数组,让代理对象与被代理对象有相同的方法,代理谁就写谁的接口。
            • .getClass().getInerfaces()
          • new InvocationHandler():写的是如何代理,一般都是该接口的实现类。
            • invoke()方法:执行被代理对象的任何接口方法都会经过该方法。
            • 参数proxy:代理对象的引用。
            • 参数method:当前执行的方法。
            • 参数args:当前执行方法所需的参数。
            • 返回值:和被代理对象相同。
          • 如果被代理的类不实现任何接口,就无法使用基于接口的动态代理。
      • 基于子类的代理:

        • 涉及的类:EnHancer。
      • 提供者:第三方cglib库。

    • 如何创建基于子类的代理对象:

      • 使用EnHancer类中的create方法。

      • 被代理类不能是最终类。

      • 参数:

        • Class:代表用于指定被代理对象的字节码。

          • .getClass()
        • Callback:用于提供增强的方法。

          • 一般写的是该接口的子实现类MethodInterceptorintercept()

            • 参数:新增了methodProxy当前执行方法的代理对象。
    • 执行被代理对象的所有方法都会执行此方法。

    • 使用基于接口的动态代理实现事务管理:

      • 创建工厂类 BeanFactory,实现对 AccountService 基于接口的动态代理:

        public class BeanFactory {
        
            private AccountService accountService;
            private TransactionManager txManager;
        
            public void setAccountService(AccountService accountService) {
                this.accountService = accountService;
            }
        
            public void setTxManager(TransactionManager txManager) {
                this.txManager = txManager;
            }
        
            public AccountService getAccountService() {
                return (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                        accountService.getClass().getInterfaces(), new InvocationHandler() {
                            @Override
                            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                                System.out.println(method.getName());
                                if(!"transfer".equals(method.getName())) {
                                    return method.invoke(accountService, objects);
                                }
                                Object value = null;
                                try {
                                    txManager.beginTransaction();
                                    value = method.invoke(accountService, objects);
                                    System.out.println("提交");
                                    txManager.commit();
                                    return value;
                                } catch (Exception e) {
                                    txManager.rollback();
                                    System.out.println("回滚");
                                    throw new RuntimeException(e);
                                } finally {
                                    txManager.release();
                                    System.out.println("释放");
                                }
                            }
                        });
            }
        }
        
      • 在 AcountServiceImpl 中恢复到无异常处理的状态:

        public void transfer(String sourceName, String targetName, float money) {
            Account source = accountDao.findAccountByName(sourceName);
            Account target = accountDao.findAccountByName(targetName);
            source.setMoney(source.getMoney()-money);
            target.setMoney(target.getMoney()+money);
            accountDao.updateAccount(source);
            accountDao.updateAccount(target);
        }
        
      • 在 bean.xml 中注入:

        <beans>
        	<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
        
            <bean id="beanFactory" class="cn.iwehdio.factory.BeanFactory">
                <property name="txManager" ref="txManager"></property>
                <property name="accountService" ref="accountService"></property>
            </bean>
        </beans>
        
      • 与 test 测试类整合:

        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(locations = "classpath:bean.xml")
        public class test {
        
            @Autowired
            @Qualifier("proxyAccountService")
            private AccountService service;
            
            @Test
            public void trans() {
                service.transfer("aaa","bbb",100);
            }
        }
        
    • 基于子类的动态代理:

      public class BeanFactory00 {
      
          private AccountService accountService;
          private TransactionManager txManager;
      
          public void setAccountService(AccountService accountService) {
              this.accountService = accountService;
          }
          public void setTxManager(TransactionManager txManager) {
              this.txManager = txManager;
          }
      
          public AccountService getAccountService() {
              return (AccountService) Enhancer.create(accountService.getClass(), new MethodInterceptor() {
                  @Override
                  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                      System.out.println(method.getName());
                      if(!"transfer".equals(method.getName())) {
                          return method.invoke(accountService, objects);
                      }
                      Object value = null;
                      try {
                          txManager.beginTransaction();
                          value = method.invoke(accountService, objects);
                          System.out.println("提交");
                          txManager.commit();
                          return value;
                      } catch (Exception e) {
                          txManager.rollback();
                          System.out.println("回滚");
                          throw new RuntimeException(e);
                      } finally {
                          txManager.release();
                          System.out.println("释放");
                      }
                  }
              });
          }
      }
      

    3、AOP

    面向切片编程,通过预编译和运行期动态代理实现程序功能的统一维护。是函数式编程了一种衍生范型,可以对业务逻辑的各个部分进行隔离,使得业务逻辑各部分的耦合性降低,提高可重用性。

    • 作用:在程序运行期间,不修改源码对已有方法进行增强。

    • 实现方式:动态代理。

    • Spring中的AOP:通过配置的方式进行动态代理。

    • AOP相关术语:

      • Joinpoint 连接点:指那些被拦截到的点。在Spring中可以指业务层接口中的所有方法被拦截。
      • Pointcut 切入点:指要对哪些连接点进行拦截。在Spring中可以指业务层接口中被拦截并被实际增强的方法。
      • 比如业务层接口被代理,接口中的一个方法并没有被实际增强,这个方法是连接点而不是切入点。
      • Advice 通知/增强:指拦截到连接点后所要做的事情。
      • 在切入点方法调用之前的是前置通知,之后是后置通知,catch 中的是异常通知,final 中的是最终通知,整个 invoke 方法是环绕通知。
      • Introduction 引介:一种特殊的通知。
      • Target 目标对象:被代理的目标对象。
      • Weaving 织入:把增强应用到目标对象来创建新的代理对象的过程。
      • Proxy 代理:一个类被织入增强后,就产生一个结果代理类。
      • Aspect 切面:是切入点和通知的对应关系,即被增强的方法调用了那些通知。
    • 通过XML进行Spring的AOP配置:

      • 导入坐标,用于解析切入点表达式:

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        
      • 编写业务层的类和演示用工具类:

        //AccountService.java
        public interface AccountService {
            //模拟保存账户
            void saveAccount();
            //模拟更新账户
            void updateAccount(int i);
            //模拟删除用户
            int deleteAccount();
        }
        
        //AccountServiceImpl.java
        public class AccountServiceImpl implements AccountService {
            @Override
            public void saveAccount() {
                System.out.println("保存");
            }
            @Override
            public void updateAccount(int i) {
                System.out.println("更新");
            }
            @Override
            public int deleteAccount() {
                System.out.println("删除");
                return 0;
            }
        }
        
        //Logger.java
        public class Logger {
            //打印日志,在切入点方法(业务层方法)之前执行
            public void printLog() {
                System.out.println("printlog记录日志");
            }
        }
        
      • 配置AOP约束和IOC配置:

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:aop="http://www.springframework.org/schema/aop"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">
        
            <bean id="accountService" class="cn.iwehdio.service.Impl.AccountServiceImpl"></bean>
        
        </beans>
        
      • AOP配置步骤:

        1. 把通知的Bean也交给Spring管理。

        2. 使用<aop:config>标签表明开始AOP的配置。

        3. 使用<aop:aspect>标签表明配置切面。

          • id 属性:给切面提供一个唯一索引。
          • ref 属性:指定通知类bean的id。
        4. <aop:aspect>标签内部使用对应标签配置通知类型。

          • <aop:before>标签表示前置通知。

            • method 属性:用于指定通知类中那个方法是前置通知。
            • pointcut 属性:指定切入点表达式,含义是指的是对业务层中的那些方法进行增强。
          • 切入点表达式:execution(表达式)

            • 表达式:访问修饰符 返回值 全类名.方法名(参数列表)
            • 全通配写法:* *..*.*(..)
            • 访问修饰符可以省略。
            • 返回值可以使用通配符,表示任意返回值。
            • 包名可以使用通配符,表示任意包,但是有几级包就需要写几个*.
            • 包名可以使用..表示当前包及其子包。
            • 类名和方法名都可以使用*实现通配。
            • 参数列表可以直接写数据类型,基本类型写名称,引用类型写全类名。
            • 通配符表示任意个参数,但是必须有参数。
            • ..表示任意个参数,没有参数也可以。
            • 一般而言,切入点写到业务层实现类下的所有方法。
          • 通用切入点表达式<aop:pointcut>

            • 如果写在<aop:aspect>内部,只能当前切面使用。
            • 如果写在<aop:aspect>外部,所有切面都可以使用,但是必须在切面之前。
            <aop:pointcut id="pt" expression="execution(* cn.iwehdio.service.Impl.*.*(..))"/>
            <aop:before method="beforePrintLog" pointcut-ref="pt"></aop:before>
            
        5. 完整配置:

        <bean id="accountService" class="cn.iwehdio.service.Impl.AccountServiceImpl"></bean>
        <bean id="logger" class="cn.iwehdio.utils.Logger"></bean>
        <aop:config>
            <aop:aspect id="logAdvice" ref="logger">
                <!-- 配置通知类型,建立通知方法和切入点方法的关联 -->
                <aop:before method="printLog" pointcut="execution(public void cn.iwehdio.service.Impl.AccountServiceImpl.saveAccount())"></aop:before>
            </aop:aspect>
        </aop:config>
        
      • 测试类:

        public class AOPTest {
            public static void main(String[] args) {
                ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
                AccountService accountService = (AccountService) ac.getBean("accountService");
                accountService.saveAccount();
            }
        }
        
    • 常用的通知类型:

      • 在Logger 中添加四种通知:

        public class Logger {
            public void beforePrintLog() {
                System.out.println("前置通知printlog记录日志");
            }
            public void afterReturnPrintLog() {
                System.out.println("后置通知printlog记录日志");
            }
            public void afterThrowingPrintLog() {
                System.out.println("异常通知printlog记录日志");
            }
            public void finalPrintLog() {
                System.out.println("最终通知printlog记录日志");
            }
        }
        
      • bean.xml中的通知配置:

        <aop:config>
            <aop:aspect id="logAdvice" ref="logger">
                <!-- 配置通知类型,建立通知方法和切入点方法的关联 -->
                <aop:before method="beforePrintLog" pointcut="execution(* cn.iwehdio.service.Impl.*.*(..))"></aop:before>
                <aop:after-returning method="afterReturnPrintLog" pointcut="execution(* cn.iwehdio.service.Impl.*.*(..))"></aop:after-returning>
                <aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* cn.iwehdio.service.Impl.*.*(..))"></aop:after-throwing>
                <aop:after method="afterPrintLog" pointcut="execution(* cn.iwehdio.service.Impl.*.*(..))"></aop:after>
            </aop:aspect>
        </aop:config>
        
      • 环绕通知:

        • bean.xml配置:
        <aop:around method="aroundPrintLog" pointcut-ref="pt"></aop:around>
        
        • 配置环绕通知后,切入点方法没有执行,但是环绕通知执行了。

        • 在环绕通知中,需要显式调用切入点方法。

        • 使用ProceedingJoinPoint接口的方法proceed()方法调用切入点方法,使用getArgs()方法获取参数:

          public Object aroundPrintLog(ProceedingJoinPoint pjp) {
              Object value = null;
              Object[] args = pjp.getArgs();
              try {
                  System.out.println("环绕通知(前置)printlog记录日志");
                  value = pjp.proceed(args);
                  System.out.println("环绕通知(后置)printlog记录日志");
              } catch (Throwable throwable) {
                  System.out.println("环绕通知(异常)printlog记录日志");
                  throwable.printStackTrace();
              } finally {
                  System.out.println("环绕通知(最终)printlog记录日志");
              }
              return value;
          }
          
    • 基于注解的AOP配置:

      • 基于注解的约束:

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:aop="http://www.springframework.org/schema/aop"
               xmlns:context="http://www.springframework.org/schema/context"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd
                http://www.springframework.org/schema/context
                http://www.springframework.org/schema/aop/spring-context.xsd">
        
      • 配置Spring创建容器时扫描的包,并把业务层类和Logger类载入IOC容器。

      • 配置Spring开启AOP注解支持:

        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
        
        • 也可以使用注解@EnableAspectJAutoProxy
      • @Aspect表示当前类是一个切面类,在Logger类上。

      • @Before(切入点表达式):前置通知标签。

      • @AfterReturning(切入点表达式):后置通知标签。

      • @AfterThrowing(切入点表达式):异常通知标签。

      • @After(切入点表达式):最终通知标签。

      • @Around(切入点表达式):环绕通知标签。

      • 通用切入点表达式:

        @Pointcut("表达式")
        private void pt(){}
        @Before("pt()")
        public void before PrintLog(){}
        
      • Spring基于注解的配置,后置通知的执行顺序有问题。


    iwehdio的博客园:https://www.cnblogs.com/iwehdio/
  • 相关阅读:
    java源码--ArrayList
    java源码--AbstractList
    java中public protected friendly private作用域
    设置springmvc全局异常
    Java
    Java
    Java
    Java
    Java
    Java
  • 原文地址:https://www.cnblogs.com/iwehdio/p/13081101.html
Copyright © 2020-2023  润新知