Spring

Spring

目录

一、Spring概述

1. Spring特点

  • Spring是全栈式轻量级开源框架

    全栈式(full-stack):Spring对主流技术和第三方开源框架进行整合,同时对三层架构提供解决方案

    web层:Spring MVC

    service层:Spring的核心IOC用于完成事务控制和对象管理

    dao层:Spring JdbcTemplate

    轻量级:轻量级和重量级的划分主要依据是使用的服务数、启动加载的资源量和耦合度

  • Spring两大核心:

    IOC:控制反转:将对象的创建权交给Spring

    将使用new关键字创建对象的方式替代为:Spring在启动时会创建一些对象存储在容器中,当需要用到某个对象时直接从容器中获取即可

    AOP:面向切面编程:在不修改源代码的情况下,对方法进行增强

    AOP底层是对动态代理的封装

2. Spring优势

  • 方便解耦,简化开发

    耦合:是指程序间的依赖关系,例如:程序编译期依赖

    // 示例:注册驱动
    // 存在编译期依赖(new对象需要有对应的jar包或maven依赖),耦合重的体现
    DriverManager.registerDriver(new com.mysql.jdbc.Driver());
    

    解耦:降低程序间的依赖关系,例如:使用反射将程序调整为编译期不依赖,运行期依赖

    // 编译期不依赖(传参中只是字符串),运行期才依赖,但是存在硬编码问题建议使用配置文件+反射
    Class.forName("com.mysql.jdbc.Driver");
    

二、IOC(Inverse of Control)

1. IOC概述

  • 控制:指的是对象的控制权限(创建、销毁)

    反转:指的是对象控制权由原来由开发者在类中手动控制反转到由Spring容器控制

    传统方式创建对象需要在类中手动new对象,IOC方式需要一个对象可以直接从Spring的IOC容器中获取(Spring在启动时会创建一些对象存储在IOC容器中)

2. 手动实现IOC容器功能

  • IOC容器的本质是一个Map集合,key为bean id,value为id对应实现类的对象

  • 实现过程

    1. 导入依赖dom4j(解析器)、jaxen(XPath)、junit

    2. 定义XML用于定义指定的id和class全类名(可以定义多个bean)

      <?xml version="1.0" encoding="UTF-8" ?> 
      <beans> 
          <bean id="userDao" class="com.example.dao.impl.UserDaoImpl"></bean> 
      </beans>
      
    3. 定义一个BeanFactory工具类用于解析XML中的bean标签的id和class属性,然后使用反射创建类名对应的实例(这个过程在使用静态代码块完成,测试类中调用BeanFactory时就已经完成),在BeanFactory中添加getBean方法通过传入id获取指定id的对象

      每次解析完成并生成指定的对象后,将id和对象放入BeanFactory工具类的map集合(模拟的IOC容器)中

      public class BeanFactory {
          
          private static Map<String,Object> iocmap = new HashMap<>();
      
          // 程序启动时,初始化对象实例
          static {
      
              //1.读取配置文件
              InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
      
              //2.解析xml(dom4j)
              SAXReader saxReader = new SAXReader();
              try {
                  Document document = saxReader.read(resourceAsStream);
               //3.编写xpath表达式
                  String xpath = "//bean";
      
               //4.获取到所有的bean标签
                  List<Element> list = document.selectNodes(xpath);
      
               //5.遍历并使用反射创建对象实例,存到map集合(ioc容器)中
                  for (Element element : list) {
                      String id = element.attributeValue("id");
                      //className : com.example.dao.impl.UserDaoImpl
                      String className = element.attributeValue("class");
                      //使用反射生成实例对象
                      Object o = Class.forName(className).newInstance();
                      // 存到map中 key:id value:o
                      iocmap.put(id,o);
                  }
      
              } catch (DocumentException | ClassNotFoundException e) {
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  e.printStackTrace();
              } catch (InstantiationException e) {
                  e.printStackTrace();
              }
      
          }
              public static Object getBean(String beanId){
                  Object o = iocmap.get(beanId);
                  return o;
              }
      }
      
    4. 利用BeanFactory创建对象

      案例:改进ServiceImpl的代码,生成Dao层实现类的对象

      相比原先的代码,可以解决存在的编译期依赖(耦合重)问题,同时又可以解决直接使用反射生成对象的硬编码问题

      public class UserServiceImpl implements IUserService {
      
      
          public void save() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
              // 调用dao层方法 传统方式 :存在编译期依赖:耦合重
              //IUserDao userDao = new UserDaoImpl();
      
              //反射
              // IUserDao userDao = (IUserDao) Class.forName("com.example.dao.impl.UserDaoImpl").newInstance();
      
              IUserDao userDao = (IUserDao) BeanFactory.getBean("userDao");
              userDao.save();
      
          }
      
      }
      

3. Spring基于XML的开发方式

  • 开发步骤

    1. 在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_quickstart</artifactId>
          <version>1.0-SNAPSHOT</version>
      
          <properties>
              <maven.compiler.source>11</maven.compiler.source>
              <maven.compiler.target>11</maven.compiler.target>
          </properties>
      
          <dependencies>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-context</artifactId>
                  <version>5.1.5.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>junit</groupId>
                  <artifactId>junit</artifactId>
                  <version>4.12</version>
              </dependency>
          </dependencies>
      
      </project>
      
    2. 创建bean id对应的接口和实现类

    3. 在项目resources文件夹中新建Spring核心配置文件applicationContext.xml,并在文件中配置bean的id和实现类全类名

      id是唯一标识,不可重名

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <beans>
              <bean id="userDao" class="com.example.dao.impl.UserDaoImpl"></bean>
          </beans>
      </beans>
      
    4. 在测试类中创建ApplicationContext接口的实现类对象用于加载配置文件并创建指定的实现类对象放入IOC容器中,最后执行getBean方法获取指定id的bean

4. Spring加载配置的相关接口

  • BeanFactory是IOC容器的核心接口,它定义了IOC的基本功能

    ApplicationContext接口是BeanFactory接口的子接口

  • BeanFactory接口中的常用方法getBean

    getBean有多种重载方法,一般传入XML中的bean id,还可以传入bean id对应的类名.classClass类对象,通过传入Class类对象来创建对象一般建议同时传入bean id和Class 类对象,防止传入接口全类名同时匹配了XML中配置多个带接口实现类全类名的bean标签

  • BeanFactory接口的实现类

    • XmlBeanFactory
  • ApplicationContext接口的实现类

    • ClassPathXmlApplicationContext用于传入当前项目下resources文件夹下的配置文件
    • FileSystemXmlApplicationContext用于传入绝对路径的配置文件(包含盘符)
  • BeanFactory和ApplicationContext主要区别:配置文件加载和创建对象的时机不同

    BeanFactory接口的实现类在第一次调用getBean方法时,加载配置文件并创建指定对象的实例

    ApplicationContext在Spring容器启动时(第一次调用ApplicationContext的实现类构造方法时),加载配置文件并创建配置中所有的对象实例

  • 示例Code

    @Test
    public void test1(){
        // 获取到了spring上下文对象,借助上下文对象可以获取到IOC容器中的bean对象, 加载的同时就创建了bean对象存到容器中
        ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
        IUserDao userDao = classPathXmlApplicationContext.getBean("userDao",IUserDao.class);
    
        // 调用方法
        userDao.save();
    }
    
    @Test
    public void test2(){
        // 核心接口,不会创建bean对象存到容器中
        BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    
        //getBean的时候才真正创建bean对象
        IUserDao userDao = (IUserDao) xmlBeanFactory.getBean("userDao");
    
        // 调用方法
        userDao.save();
    }
    

5. Spring核心配置文件

  • Spring核心配置文件主要是applicaionContext.xml

  • bean标签:创建对象并放到Spring的IOC容器中

    • id:表示Bean实例在Spring中的唯一标识

    • class:类全路径(一般是接口的实现类)

      默认调用的是类中的无参构造,若没有无参构造则不能创建

    • scope:scope的取值可以是singleton、prototype、request、session、global session

      singleton:默认值,创建出来的bean是单例的

      对象的创建时机:当应用加载时,创建容器时,对象被创建

      prototype:创建出来的bean是多例的,每次从容器中获取都会创建一个新的对象

      对象的创建时机:使用对象时(getBean),创建新的对象实例

    • init-method:指定类中的初始化方法名称

    • destroy-method:指定类中销毁方法名称

  • constructor-arg标签:使用带参构造方法的属性注入

    • name:属性名称
    • value:注入的普通属性
    • ref:注入的对象引用值
  • property标签:使用set方法的属性注入

    • name:属性名称
    • value:注入的普通属性值
    • ref:注入的对象引用值
    • property子标签:list、set、array、map、props
  • import标签:导入其他的Spring分配置文件

6. Bean实例化的三种方式

  • 无参构造方法实例化(默认方式)

    使用场景:对自定义类根据默认其无参构造方法来创建类对象

  • 工厂静态方法实例化

    • 使用方式:bean标签中添加属性factory-method用于保存静态方法的名称
    • 使用场景:对jar包中的静态方法,用于避免多次通过类名调用,可以直接将该静态方法的返回值结果保存到IOC容器中,方便直接使用bean id调用
  • 工厂普通方法实例化

    • 使用方式:先将jar对应的类作为bean,再创建另一个bean使用factory-bean引入刚才的bean id,添加属性factory-method用于保存方法的名称

    • 使用场景:对jar包中的普通方法,可以直接将方法的返回值结果保存到IOC容器中,方便直接使用bean id调用

7. Spring依赖注入

7.1 依赖注入概述

  • 依赖注入(Dependency Injection):是Spring框架核心IOC的具体实现
  • 之前可以通过Spring相关API手动地维护业务层和持久层的依赖关系,使用Spring后,就让Spring来维护,通过框架把持久层对象传入业务层

7.2 依赖注入的方式

  • 构造方法

    为Service层实现类中添加有参构造方法,参数列表中的参数为Dao层接口类型

    public class UserServiceImpl implements UserService {
        private UserDao userDao;
    
        public UserServiceImpl(UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public void save() {
    //        1.
    //        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    //        UserDaoImpl userDao = (UserDaoImpl)applicationContext.getBean("userDao");
    //        userDao.save();
    //        2.
            userDao.save();
        }
    }
    

    为核心配置文件中bean标签添加子标签

    constructor-arg标签的两种属性方式:

    1. index:Service实现类构造方法参数索引

      type:当前参数类型的全类名

      ref:引用当前beans中的其他bean的id作为实参传入构造方法

    2. name:Service实现类成员变量对应构造方法的形参名

      ref:引用当前beans中的其他bean的id作为实参传入构造方法

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <beans>
            <bean id="userDao" class="com.example.dao.impl.UserDaoImpl"></bean>
            <bean id="userService" class="com.example.service.impl.UserServiceImpl">
    <!--            1.-->
    <!--            <constructor-arg index="0" type="com.example.dao.UserDao" ref="userDao"></constructor-arg>-->
    <!--            2.-->
                <constructor-arg name="userDao" ref="userDao"></constructor-arg>
            </bean>
        </beans>
    </beans>
    
  • set方法

    • 注入普通类型数据(基本数据类型和String类型)

      Spring核心配置文件下bean标签的子标签property的name属性为注入类中成员变量名,value属性为普通类型的成员变量值

      public class UserDaoImpl implements UserDao {
          private Integer age;
          private String name;
      
          public void setAge(Integer age) {
              this.age = age;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          @Override
          public void save() {
              System.out.println(age);
              System.out.println(name);
              System.out.println("success to save...");
          }
      }
      
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <beans>
              <bean id="userDao" class="com.example.dao.impl.UserDaoImpl">
                  <property name="age" value="18"></property>
                  <property name="name" value="Jeff"></property>
              </bean>
          </beans>
      </beans>
      
    • 注入引用数据类型

      Spring核心配置文件下bean标签的子标签property的name属性为注入类中成员变量名,ref属性为引用类型的成员变量值

      public class UserServiceImpl implements UserService {
          private UserDao userDao;
      
          public void setUserDao(UserDao userDao) {
              this.userDao = userDao;
          }
      
          @Override
          public void save() {
              userDao.save();
          }
      }
      
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <beans>
              <bean id="userDao" class="com.example.dao.impl.UserDaoImpl">
                  <property name="age" value="18"></property>
                  <property name="name" value="Jeff"></property>
              </bean>
              <bean id="userService" class="com.example.service.impl.UserServiceImpl">
                  <property name="userDao" ref="userDao"></property>
              </bean>
          </beans>
      </beans>
      
    • 注入集合数据类型

      • List集合
      • Set集合
      • Array数组
      • Map集合
      • Properties
  • p命名空间注入

    • p命名空间注入的本质也是set方法注入,好处是比set方法注入更加方便,不用添加property子标签

8. Spring配置文件模块化

  • 配置文件中的内容过多,可以进行拆分

  • 配置文件拆分的分类

    • 按照层级进行拆分(applicationContext-dao.xml、applicationContext-service.xml)
    • 按业务模块进行拆分(applicationContext-user.xml、applicationContext-products.xml)
  • 拆分完成后,在主配置文件中完成整合的方式

    1. 并列的多个配置文件:ClassPathXmlApplicationContext的参数列表中传入多个配置文件名的字符串参数

    2. 主从配置文件:import标签引入拆分后的配置

9. Spring基于XML的开发案例

  • 案例:基于Spring的xml配置实现账户的CRUD案例

10. Spring基于注解的开发方式

  • 常见注解

    Spring

  • 其他注解

    通过在Spring核心配置类中添加注解的方式,代替了Spring核心配置文件

11. Spring整合JUnit

  • 之前在测试类中需要先调用Spring API去加载核心配置文件或核心配置类才能获取配置的bean并创建对应的对象,Spring提供了可以在测试类上添加注解的方式来引入核心配置文件或核心配置类

  • 示例Code

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration({"classpath:applicationContext.xml"})
    public class AccountServiceTest {
        @Autowired
        private AccountService accountService;
    
        @Test
        public void testTransfer() {
            accountService.transfer();
        }
    }
    

三、AOP(Aspect Oriented Programming)

1. 常见的动态代理方式

  • JDK代理:是基于接口的代理方式,要求接口实现类作为目标对象类(被代理类),动态代理生成的代理对象和目标对象(被代理类对象)将会保持同一级别

    JDK代理要求被代理类至少实现一个接口

    JDK动态代理工厂类调用方法createAccountSericeJDKProxy可以生成代理对象,当代理对象调用接口中的任意方法时,那么都会执行InvocationHandler中invoke方法

    method.invoke(accountService, args);产生代理对象的同时会调用目标对象中的指定方法(让被代理对象的原方法执行),在此句之前和之后可以添加用于增强被代理对象的方法

    /*
        JDK动态代理目标对象类(被代理类)
     */
    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
    
        @Autowired
        private AccountDao accountDao;
    
        @Override
        public void transfer(String outUser, String inUser, Double money) {
                accountDao.out(outUser, money);
                accountDao.in(inUser, money);
        }
    }
    
    /*
        JDK动态代理工厂类
     */
    @Component
    public class JDKProxyFactory {
    
        @Autowired
        private AccountService accountService;
    
    
        @Autowired
        private TransactionManager transactionManager;
    
        /*
            采用JDK动态代理技术来生成目标类的代理对象
             ClassLoader loader, : 类加载器:借助被代理对象获取到类加载器
             Class<?>[] interfaces, : 被代理类所需要实现的全部接口
             InvocationHandler h : 当代理对象调用接口中的任意方法时,那么都会执行InvocationHandler中invoke方法
         */
        public AccountService createAccountSericeJDKProxy(){
    
            AccountService accountServiceProxy= (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
    
                @Override      //   proxy: 当前的代理对象引用   method:被调用的目标方法的引用   args:被调用的目标方法所用到的参数
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    try {
                            if(method.getName().equals("transfer")) {
                                System.out.println("进行了前置增强");
                                // 手动开启事务:调用事务管理器类中的开启事务方法
                                transactionManager.beginTransaction();
    
                                // 让被代理对象的原方法执行
                                method.invoke(accountService, args);
                                System.out.println("进行了后置增强");
    
                                // 手动提交事务
                                transactionManager.commit();
                            }else {
    
                                method.invoke(accountService, args);
                            }
                    } catch (Exception e) {
                        e.printStackTrace();
                        // 手动回滚事务
                        transactionManager.rollback();
                    } finally {
                        // 手动释放资源
                        transactionManager.release();
                    }
                    return null;
                }
            });
            // 返回生成的代理对象
            return  accountServiceProxy;
        }
    }
    
  • CGLIB代理:是基于父类的代理方式,动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行增强

    CGLIB代理要求被代理类不能被final修饰,因为是基于父类的代理方式

    CGLIB动态代理工厂类调用方法createAccountServiceCglibProxy可以生成代理对象,当代理对象调用目标对象中原方法时,那么都会执行MethodInterceptor类型对象中的intercept方法

    /*
        CGLIB代理目标对象类(被代理类)
     */
    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
    
        @Autowired
        private AccountDao accountDao;
    
        @Override
        public void transfer(String outUser, String inUser, Double money) {
                accountDao.out(outUser, money);
                accountDao.in(inUser, money);
        }
    }
    
    /*
        该类就是采用cglib动态代理来对目标类(AccountServiceImpl)进行方法(transfer)的动态增强(添加上事务控制)
     */
    @Component
    public class CglibProxyFactory {
    
        @Autowired
        private AccountService accountService;
    
        @Autowired
        private TransactionManager transactionManager;
    
        public AccountService createAccountServiceCglibProxy(){
            // 编写cglib对应的API来生成代理对象进行返回
            // 参数1 : 目标类的字节码对象
            // 参数2:  动作类,当代理对象调用目标对象中原方法时,那么会执行intercept方法
            AccountService accountServiceproxy = (AccountService) Enhancer.create(accountService.getClass(), new MethodInterceptor() {
    
                // o : 代表生成的代理对象   method:调用目标方法的引用  objects:方法入参    methodProxy:代理方法
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
                    try {
                        // 手动开启事务:调用事务管理器类中的开启事务方法
                        transactionManager.beginTransaction();
    
                        method.invoke(accountService, objects);
    
                        transactionManager.commit();
                    } catch (Exception e) {
                        e.printStackTrace();
                        // 手动回滚事务
                        transactionManager.rollback();
                    } finally {
                        // 手动释放资源
                        transactionManager.release();
                    }
                    return null;
                }
            });
            // 返回生成的代理对象
            return accountServiceproxy;
        }
    }
    

2. AOP相关术语

  • Target(目标对象):被代理类
  • Proxy(代理):生成代理对象
  • Joinpoint(连接点):被代理类中可以被拦截增强的方法
  • Pointcut(切入点):被代理类中正真被拦截的方法
  • Advice(通知/增强):用于增强业务逻辑
    • 前置通知
    • 后置通知
    • 异常通知
    • 最终通知
    • 环绕通知
  • Aspect(切面):切入点和通知结合的过程(重写invoke方法或重写intercept方法的部分)
  • Weaving(织入):把增强应用到被代理类来创建新的代理对象的过程(调用Proxy.newProxyInstanceEnhancer.create的过程)

3. 基于XML的AOP开发方式

3.1 AOP开发和运行

  • 开发阶段

    • 切入点:编写目标类中的目标方法

      public class AccountServiceImpl implements AccountService {
          /**
           * 要进行拦截的方法
           */
          @Override
          public void transfer() {
              System.out.println("success to execute transaction");
          }
      }
      
    • 通知:把公用代码抽取出来,制作成通知类(增强功能方法)

      public class MyAdvice {
          public void before() {
              System.out.println("before method...");
          }
      }
      
    • 切面:在配置文件中,声明切入点和通知之间的关系

      <?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">
          <!--目标类交给IOC容器-->
          <bean id="accountService" class="com.example.service.impl.AccountServiceImpl"></bean>
          <!--通知类交给IOC容器-->
          <bean id="myAdvice" class="com.example.advice.MyAdvice"></bean>
          <!--AOP配置-->
          <aop:config>
              <!--配置切面:切点+通知-->
              <aop:aspect ref="myAdvice">
                  <aop:before method="before" pointcut="execution(public void com.example.service.impl.AccountServiceImpl.transfer())"></aop:before>
              </aop:aspect>
          </aop:config>
      </beans>
      
  • 运行阶段

    • 主要由Spring完成:一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行

3.2 切点表达式使用方式

  • 切点表达式概述

    Spring

  • 示例Code

    aop:before子标签表示当前通知是前置通知;

    method属性为指定通知类下的方法;

    指定切入点为当前项目下所有包的方法(全通配方式)

    Spring

    aop:before子标签表示当前通知是前置通知;

    method属性为指定通知类下的方法;

    指定切入点为被代理类下所有方法

    Spring

    一个切入点中多个通知时,使用aop:pointcut抽取切点表达式

    Spring

3.3 通知标签使用方式

  • 通知标签概述

    同时添加后置通知和异常通知最终只执行其中一个

    Spring

  • 环绕通知的使用方式

    通过对切点对象执行的方法添加try catch来一步实现前四种通知

    /*
        通知类
     */
    public class MyAdvice {
    
        public void before(){
            System.out.println("前置通知执行了....");
        }
    
        public void afterReturning(){
            System.out.println("后置通知执行了....");
        }
    
        public void afterThrowing(){
    
            System.out.println("异常通知执行了....");
        }
    
        public void after(){
            System.out.println("最终通知执行了....");
        }
    
        // Proceeding JoinPoint : 正在执行的连接点:切点对象
        public Object around(ProceedingJoinPoint pjp){
    
            // 切点方法执行
            Object proceed = null;
            try {
                System.out.println("前置通知执行了");
                proceed = pjp.proceed();
                System.out.println("后置通知执行了");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                System.out.println("异常通知执行了");
            }finally {
                System.out.println("最终通知执行了");
            }
    
            return proceed;
        }
    }
    
    <?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">
    
        <!--目标类交给IOC容器-->
        <bean id="accountServcie" class="com.example.servlet.impl.AccountServiceImpl"></bean>
    
        <!--通知类交给IOC容器-->
        <bean id="myAdvice" class="com.example.advice.MyAdvice"></bean>
    
        <!--AOP配置-->
        <aop:config>
            <!--抽取的切点表达式-->
            <aop:pointcut id="myPointcut" expression="execution(* com.example.servlet.impl.AccountServiceImpl.*(..))"/>
    
            <!--配置切面:切入点+通知-->
            <aop:aspect ref="myAdvice">
           <!--     <aop:before method="before" pointcut-ref="myPointcut"/>
                <aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
                <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
                <aop:after method="after" pointcut-ref="myPointcut"/>-->
                <aop:around method="around" pointcut-ref="myPointcut"/>
            </aop:aspect>
        </aop:config>
    </beans>
    

4. 基于注解的AOP开发方式

4.1 将通知类升级为切面类两种方式

  • 在通知注解中配置切点表达式

    @Component 
    @Aspect 
    public class MyAdvice { 
        @Before("execution(* com.example.service.impl.AccountServiceImpl.*(..))") 
        public void before() { 
            System.out.println("前置通知..."); 
        } 
    }
    
  • 抽取切点表达式,在通知注解中引用切点表达式

    @Component 
    @Aspect 
    public class MyAdvice { 
        @Pointcut("execution(* com.example.service.impl.AccountServiceImpl.*(..))") 
        public void myPoint(){} //该方法名可以自定义
        
        @Before("MyAdvice.myPoint()") 
        public void before() { 
            System.out.println("前置通知..."); 
        }
    }
    

4.2 开启AOP自动代理

  • XML方式

    <!--组件扫描--> 
    <context:component-scan base-package="com.example"/>
    <!--aop的自动代理--> 
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  • 纯注解方式

    @Configuration 
    @ComponentScan("com.example") 
    @EnableAspectJAutoProxy //替代 <aop:aspectj-autoproxy /> 
    public class SpringConfig {
        
    }
    

4.3 测试基于注解的AOP开发方式

  • 案例中被代理类(目标实现类)是实现了接口的实现类,Spring默认按照JDK动态代理方式生成代理对象

    public class AccountServiceImpl implements AccountService { 
        @Override 
        public void transfer() { 
            System.out.println("转账业务..."); 
        } 
    }
    
  • 测试类Code

    @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") 
    class AccountServiceTest {
        
        @Autowired 
        private AccountService accountService; 
        
        @Test 
        public void testTransfer() throws Exception {
            // 配置了AOP自动代理后,此时注入的是Spring生成的AccountService类型代理对象
            accountService.transfer(); 
        } 
    }
    

四、JdbcTemplate

  • JdbcTemplate是Spring框架中提供的一个模板对象,是对原始繁琐的JDBC API对象的简单封装(作用类似于DBUtils中的QueryRunner类)

  • 核心对象

    JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSource dataSource);
    
  • 核心方法

    • int update();执行增、删、改语句
    • List<T> query();查询多个
    • T queryForObject();查询一个
  • 示例Code

    @Repository
    public class AccountDaoImpl implements AccountDao {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Override
        public List<Account> findAll() {
            String sql = "select * from account";
            List<Account> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
            return query;
        }
    
        @Override
        public Account findById(Integer id) {
            String sql = "select * from account where id = ?";
            Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), id);
            return account;
        }
    
        @Override
        public void save(Account account) {
            String sql = "insert into account values(null, ?, ?)";
            jdbcTemplate.update(sql, account.getName(), account.getMoney());
        }
    
        @Override
        public void update(Account account) {
            String sql = "update account set money = ? where name = ?";
            jdbcTemplate.update(sql, account.getMoney(), account.getName());
        }
    
        @Override
        public void delete(Integer id) {
            String sql = "delete from account where id = ?";
            jdbcTemplate.update(sql, id);
        }
    }
    

五、事务控制

1. 事务控制概述

  • 编程式

    • 开发者直接把事务的代码和业务代码耦合到一起,在实际开发中不常用

    • 实现方式:

      事务管理器通过读取事务定义参数进行事务管理,然后会产生一系列的事务状态

      PlatformTransactionManager 负责事务的管理,它是个接口,其子类负责具体工作

      TransactionDefinition 定义了事务的一些相关参数(事务隔离级别、事务传播行为等等)

      TransactionStatus 代表事务运行的一个实时状态

  • 声明式

    开发者采用配置的方式来实现的事务控制,业务代码与事务代码实现解耦合,使用的AOP思想

2. 基于XML的声明式事务控制

  • XML中实现步骤

    1. 引用tx命名空间
    2. 事务管理器通知配置
    3. 事务管理器AOP配置
  • 示例Code

    增删改时propagation属性设置为REQUIRED,read-only属性设置为false

    查找时propagation属性设置为SUPPORTS, read-only属性设置为true

    isolation属性设定为MySQL的默认隔离级别REPEATABLE_READ

    <!--事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!--事务通知的配置-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--定义事务的属性-->
        <tx:attributes>
            <tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" timeout="-1"></tx:method>
        </tx:attributes>
    </tx:advice>
    
    <!--事务aop织入配置-->
    <aop:config>
        <!--切面配置-->
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.example.service.impl.ArticleServiceImpl.*(..))"></aop:advisor>
    </aop:config>
    

3. 基于注解的声明式事务控制

  • 两种声明式事务控制的优点:注解方式更简洁,XML配置的方式更易于维护

  • 注解方式实现步骤

    1. 平台事务管理器配置(XML或注解方式)

    2. 事务通知的配置:修改service层,增加事务注解

      @Transactional注解用于定义事务属性,可以添加在指定方法上或直接添加在service层实现类上(不带属性值,使用默认的属性值)

      @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, timeout = -1, readOnly = false)
      
    3. 事务注解驱动的配置:

      修改Spring核心配置文件,开启事务注解支持(或者在配置类上添加@EnableTransactionManagement)

      <!--事务的注解支持--> 
      <tx:annotation-driven/>
      
上一篇:【最小Demo】Java RMI入门 使用Registry、Naming、Zookeeper(注册中心)实现rmi


下一篇:文件上传