Spring浅入浅出

文章目录

  • 一、程序耦合与解耦
    • 1. 工厂模式解耦
    • 2. IOC (控制反转)
      • 2.1 获取Spring容器中对象的方式
      • 2.2 Spring对工厂类Bean的配置
      • 2.3 Bean的作用范围
    • 3. Spring中的DI配置
      • 3.1 构造注入
      • 3.2 set方法注入
      • 3.3 集合类型注入
      • 3.4 配置文件自动注入
      • 3.5 注解注入
        • 3.5.1 创建对象注解
        • 3.5.2 注入数据注解
        • 3.5.3 作用范围注解
        • 3.5.4 生命周期注解(了解)
    • 3.6 Spring配置类注解
  • 二、Spring单元测试
  • 三、AOP(面向切面编程)
    • 1. 动态代理
      • 1.1 基于接口代理
      • 1.2 基于子类代理
    • 2. AOP术语
    • 3. AOP配置
      • 3.1 切点表达式
    • 4 通知分类
      • 4.1 环绕通知
    • 5. 注解实现AOP
  • 四、Spring JDBC
    • 1. Spring内置数据源
    • 2. JdbcTemplate
    • 3. JdbcDaoSupport
    • 2. Spring 声明式事务
      • 2.1 配置文件
      • 2.2 半注解配置
      • 2.3 全注解

一、程序耦合与解耦

  • 耦合:程序间的依赖关系,如业务层依赖持久层的crud
    • 包括:类之间的依赖、方法间的依赖
  • 解耦:降低程序间的依赖关系
  • 实际开发中应该做到:编译期不依赖,运行时才依赖。
  • 解耦的思路:
    • 第一步:使用反射来创建对象,避免使用new关键字。
    • 第二步:通过读取配置文件来获取要创建的对象全限定类名

1. 工厂模式解耦

  • Bean:在计算机英语中,有可重用组件的含义。
  • JavaBean:用java语言编写的可重用组件。
    • javabean > 实体类

工厂模式解耦思路:

  • 一个创建Bean对象的工厂,用工厂中的getBean方法获取需要的类对象,而不是直接new对象写死
  • 工厂的功能:
    • 需要一个配置文件来配置我们的service和dao
      • 配置的内容:唯一标识=全限定类名(key=value)
    • 工厂通过读取配置文件中配置的内容,反射创建对象

工厂模式解耦说明图

直接new对象高耦合

工厂接管创建对象来解耦

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vNfkVZQO-1592693871144)(Spring%E7%AC%94%E8%AE%B0.assets/image-20200618163946799.png)]

工厂类示例:

public class BeanFactory {
    //定义一个Properties对象
    private static Properties props;

    //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
    private static Map<String,Object> beans;

    //使用静态代码块为Properties对象赋值
    static {
        try {
            //实例化对象
            props = new Properties();
            //获取properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            //实例化容器
            beans = new HashMap<String,Object>();
            //取出配置文件中所有的Key
            Enumeration keys = props.keys();
            //遍历枚举
            while (keys.hasMoreElements()){
                //取出每个Key
                String key = keys.nextElement().toString();
                //根据key获取value
                String beanPath = props.getProperty(key);
                //反射创建对象
                Object value = Class.forName(beanPath).newInstance();
                //把key和value存入容器中
                beans.put(key,value);
            }
        }catch(Exception e){
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }

    /**
     * 根据bean的名称获取对象,单例
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }

    /**
     * 根据Bean的名称获取bean对象 多例模式
     * @param beanName
     * @return
	 */
    public static Object getBean(String beanName){
        Object bean = null;
        try {
            String beanPath = props.getProperty(beanName);
            bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象
        }catch (Exception e){
            e.printStackTrace();
        }
        return bean;
    }
}

2. IOC (控制反转)

  • 把创建对象(new 对象)的权利交给了框架(或工厂),降低程序耦合性

  • Spring中的IOC:

    • 创建对象交给spring来维护,在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
  • DI(依赖注入):依赖关系的维护被称之为依赖注入

  • 能注入的数据类型:

    • 基本类型和String
    • 其他bean类型(在配置文件中或者注解配置过的bean)
    • 复杂类型/集合类型
  • 注入方式:

    • 使用构造函数提供
    • 使用set方法提供
    • 使用注解提供

2.1 获取Spring容器中对象的方式

获取spring的Ioc核心容器,并根据id获取对象

  • ApplicationContext的三个常用实现类:
    • ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
    • FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
    • AnnotationConfigApplicationContext:它是用于读取注解创建容器的,是明天的内容。

核心容器的两个接口引发出的问题:

  • ApplicationContext:单例对象适用、采用此接口
    它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。

  • BeanFactory:多例对象使用(了解)
    它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。

示例:

public static void main(String[] args) {
    //1.获取核心容器对象
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\bean.xml");
    //2.根据id获取Bean对象
    IAccountService as  = (IAccountService)ac.getBean("accountService");
    IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);

    System.out.println(as);
    System.out.println(adao);

    //--------BeanFactory---------- 了解
    Resource resource = new ClassPathResource("bean.xml");
    BeanFactory factory = new XmlBeanFactory(resource);
    IAccountService as  = (IAccountService)factory.getBean("accountService");
    System.out.println(as);
}

2.2 Spring对工厂类Bean的配置

Spring中bean对象的创建:

  • 在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="top.tdte.service.AccountServiceImpl" />
  • 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="top.tdte.factory.InstanceFactory">bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService">bean>
  • 使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<bean id="accountService" class="top.tdte.factory.StaticFactory" factory-method="getAccountService">bean>

2.3 Bean的作用范围

bean的作用范围调整

  • 配置bean标签的scope属性:
  • 作用:用于指定bean的作用范围
  • 取值:常用的就是单例的和多例的
    • singleton:单例的(默认值)
    • prototype:多例的
    • request:作用于web应用的请求范围
    • session:作用于web应用的会话范围
    • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
<bean id="accountService" class="com.xxx.AccountServiceImpl" scope="prototype">bean>

bean对象的生命周期

  • 单例对象
    • 出生:当容器创建时对象出生
    • 活着:只要容器还在,对象一直活着
    • 死亡:容器销毁,对象消亡
    • 总结:单例对象的生命周期和容器相同
  • 多例对象
    • 出生:当我们使用对象时spring框架为我们创建
    • 活着:对象只要是在使用过程中就一直活着。
    • 死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收

<bean id="accountService" class="com.xxx.AccountServiceImpl"
          scope="prototype" init-method="init" destroy-method="destroy">bean>

3. Spring中的DI配置

配置文件约束:


<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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
beans>

3.1 构造注入

  • 标签:constructor-arg

  • 标签出现的位置:bean标签的内部

  • 标签中的属性:

    • name:构造函数中指定名称的参数赋值(常用)
    • type:要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
    • index:要注入的数据给构造函数中,指定索引位置的参数赋值(索引的位置是从0开始)
    • 以上三个用于指定给构造函数中哪个参数赋值
    • value:编写注入的值,基础类型或String类型
    • ref:指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
  • 优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。

  • 弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。

<bean id="accountService" class="top.tdte.service.impl.AccountServiceImpl">
     <constructor-arg name="name" value="tdte">constructor-arg>
     <constructor-arg name="age" value="18">constructor-arg>
     <constructor-arg name="birthday" ref="now">constructor-arg>
 bean>

 
 <bean id="now" class="java.util.Date"/>

3.2 set方法注入

  1. 标签:property
  2. 标签出现的位置:bean标签的内部
  3. 标签的属性:
    • name:用于指定注入时所调用的set方法名称
    • value:用于提供基本类型和String类型的数据
    • ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
    <property name="name" value="TEST" >property>
    <property name="age" value="21">property>
    <property name="birthday" ref="now">property>
bean>

3.3 集合类型注入

  1. 复杂类型的注入/集合类型的注入
  2. 用于给列表结构集合注入的标签:list array set
  3. 用于个K-V结构集合注入的标签:map props
  4. 结构相同,标签可以互换
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
     <property name="strArray">
         <array>
             <value>AAAvalue>
             <value>BBBvalue>
             <value>CCCvalue>
         array>
     property>
     <property name="myList">
         <array>
             <value>AAAvalue>
             <value>BBBvalue>
             <value>CCCvalue>
         array>
     property>
     <property name="mySet">
         <set>
             <value>AAAvalue>
             <value>BBBvalue>
             <value>CCCvalue>
         set>
     property>
     <property name="myMap">
         <map>
             <prop key="testC">cccprop>
             <prop key="testD">dddprop>
         map>
     property>
     <property name="myProps">
         <props>
             <entry key="testA" value="aaa">entry>
             <entry key="testB">
                 <value>BBBvalue>
             entry>
         props>
     property>
 bean>

3.4 配置文件自动注入

  • 使用到的属性:autowire

使用byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致


<bean id="dog" class="top.tdte.domain.Dog"/>

<bean id="people" class="com.xxx.People" autowire="byName" />

byType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!


<bean id="dog" class="top.tdte.pojo.Dog"/>

<bean id="people" class="com.kuang.pojo.People" autowire="byType" />

3.5 注解注入

  • 前提:能修改源文件,第三方jar包还是使用配置文件的方式进行注入
  • 引入新的约束:配置所需要的标签不是在beans的约束中,而是一个名称为context名称空间和约束中
  • 开启注解包扫描

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    
    <context:component-scan base-package="top.tdte"/>

    
    
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        
        <constructor-arg name="ds" ref="dataSource">constructor-arg>
    bean>

    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        
        <property name="driverClass" value="com.mysql.jdbc.Driver">property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy">property>
        <property name="user" value="root">property>
        <property name="password" value="1234">property>
    bean>
beans>

3.5.1 创建对象注解

他们的作用就和在XML配置文件中编写一个标签实现的功能是一样的

  • @Component:用于把当前类对象存入spring容器中
    • 属性value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
  • @Controller:在控制层使用
  • @Service:在业务层使用
  • @Repository:在持久层使用
  • 以上三个注解作用和功能与Component是相同的,属于语义注解。

3.5.2 注入数据注解

他们的作用就和在xml配置文件中的bean标签中写一个标签的作用是一样的

  • @Autowired:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功

    • 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
    • 如果Ioc容器中有多个类型匹配时:根据名称匹配,如果配有名称相匹配的,则报错
    • 出现位置: 可以是变量上,也可以是方法上
    • 细节: 在使用注解注入时,set方法就不是必须的了
  • @Qualifier:根据名称注入,它在给类成员变量注入时不能单独使用,需要与Autowired配合,但是在给方法参数注入时可以单独使用

    • 属性value:用于指定注入bean的id
  • @Resource:直接按照bean的id注入。它可以独立使用

    • 属性name:用于指定bean的id
  • 以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现,因此需要另一个注解@Value

  • 集合类型的注入只能通过XML来实现

  • @Value:用于注入基本类型和String类型的数据

    • 属性value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
      • SpEL的写法:${表达式}

3.5.3 作用范围注解

他们的作用就和在bean标签中使用scope属性实现的功能是一样的

  • @Scope:用于指定bean的作用范围
    属性value:指定范围的取值。常用取值:singleton prototype

3.5.4 生命周期注解(了解)

他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的

  • PreDestroy:用于指定销毁方法
  • @PostConstruct:用于指定初始化方法

注解示例:

@Service("accountService")
@Scope("prototype")
public class AccountServiceImpl implements AccountService {

//    @Autowired
//    @Qualifier("accountDao1")
    @Resource(name = "accountDao2")
    private IAccountDao accountDao = null;
    @Value("Ken")
	private String name;
    @PostConstruct
    public void  init(){
        System.out.println("初始化方法执行了");
    }

    @PreDestroy
    public void  destroy(){
        System.out.println("销毁方法执行了");
    }

    public void  saveAccount(){
        accountDao.saveAccount();
    }
}

3.6 Spring配置类注解

  1. @Configuration(类):标识当前类是一个配置类,可以被包扫描配置扫描到

    • 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以省略
  2. @ComponentScan(类):用于通过注解指定spring在创建容器时要扫描的包

    • 参数value:它和basePackages的作用是一样(别名),都是用于指定创建容器时要扫描的包。
      使用此注解就等同于在xml中配置了:
    <context:component-scan base-package="com.tdte" />
  1. @Bean(方法):用于把当前方法的返回值作为bean对象存入spring的ioc容器中

    • 参数name:用于指定bean的id。当不写时,默认值是当前方法的名称
    • 细节:当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,查找的方式和Autowired注解的作用是一样的
  2. @Import(类):用于导入其他的配置类

    • 参数value:用于指定其他配置类的字节码。
    • 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类,被引入的类需要添加@Configuration注解
  3. @PropertySource(类):用于指定properties文件的位置 ,并联合value注解和spEl表达式进行配置文件注入

    • 参数value:指定文件的名称和路径
    • 关键字:classpath,表示类路径下有包就在写包路径,无包可以省略
    @PropertySource("classpath:top/tdte/jdbcConfig.properties")
    
  4. @Scope(方法): 配置bean对象作用域

    • 参数:参考配置文件的方式
  5. @Qualifier(参数):配置bean方法的参数引入那个bean

    • 参数value:要引入的beanid

示例:

@Configuration
//扫描包路径
@ComponentScan("com.tdte")  
//导入其他配置类,形成父子关系
@Import(JdbcConfig.class)
//配置文件路径 用于加载${key名称}EL表达式
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
//和spring连接数据库相关的配置类
//如果不使用@Import(JdbcConfig.class)引入而是把下面这个类添加@Configuration注解,并被scan到,那么下面这个列和上面的配置类为兄弟关系
//由于是父子关系,引入配置文件的注解可以直接写在父配置文件中
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    /**
     * 用于创建一个QueryRunner对象
     * @param dataSource
     * @return
     */
    @Bean(name="runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name="ds2")
    public DataSource createDataSource(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Bean(name="ds1")
    public DataSource createDataSource1(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

jdbcConfig.properties配置文件内容:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
  • 如果完全使用了配置类方式去做,我们就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载!
ApplicationContext context = new AnnotationConfigApplicationContext(KuangConfig.class);

二、Spring单元测试

Spring整合junit的配置

<dependency>
 	 <groupId>org.springframeworkgroupId>
 	 <artifactId>spring-testartifactId>
  	 <version>5.2.3.RELEASEversion>
dependency>
  1. junit包提供的替换注解,下面这个例子是把原运行器替换为spring的测试单元
    @RunWith(SpringJUnit4ClassRunner.class)
    
  2. 告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
    @ContextConfiguration(locations = "classpath:beans.xml")
    
    • locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
    • classes:指定注解类所在地位置
  • 当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
// 使用spring中的测试类,替换原有的不能假装ioc容器的测试类
@RunWith(SpringJUnit4ClassRunner.class)
// 告诉测试类,我的配置文件或配置类在哪
@ContextConfiguration(classes = SpringConfiguration.class)
// @ContextConfiguration(locations = "classpath:beans.xml")
public class AccountServiceTest {

    @Autowired
    private IAccountService as = null;

    @Test
    public void testFindAll() {
        //3.执行方法
        List<Account> accounts = as.findAllAccount();
        for(Account account : accounts){
            System.out.println(account);
        }
    }
}

三、AOP(面向切面编程)

1. 动态代理

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

1.1 基于接口代理

  • 基于接口的动态代理:
    • 涉及的类:Proxy
    • 提供者:JDK官方
  • 如何创建代理对象:使用Proxy类中的newProxyInstance方法
  • 创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用
  • newProxyInstance方法的参数:
    • ClassLoader:类加载器
      • 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
    • Class[]:字节码数组
      • 它是用于让代理对象和被代理对象有相同方法。固定写法。
    • InvocationHandler:用于提供增强的代码
      • 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
      • 此接口的实现类都是谁用谁写。

示例:

public static void main(String[] args) {
    final Producer producer = new Producer();
    IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
            producer.getClass().getInterfaces(),
            new InvocationHandler() {
                /**
                 * 作用:执行被代理对象的任何接口方法都会经过该方法
                 * 方法参数的含义
                 * @param proxy   代理对象的引用
                 * @param method  当前执行的方法
                 * @param args    当前执行方法所需的参数
                 * @return 和被代理对象方法有相同的返回值
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //提供增强的代码
                    Object returnValue = null;

                    //1.获取方法执行的参数
                    Float money = (Float) args[0];
                    //2.判断当前方法是不是销售
                    if ("saleProduct".equals(method.getName())) {
                        returnValue = method.invoke(producer, money * 0.8f);
                    }
                    return returnValue;
                }
            });
    proxyProducer.saleProduct(10000f);
}

1.2 基于子类代理

<dependency>
    <groupId>cglibgroupId>
    <artifactId>cglibartifactId>
    <version>3.3.0version>
dependency>
  • 基于子类的动态代理:
    • 涉及的类:Enhancer
    • 提供者:第三方cglib库
  • 如何创建代理对象:使用Enhancer类中的create方法
  • 创建代理对象的要求:被代理类不能是最终类
  • create方法的参数:
    • Class:字节码
      • 它是用于指定被代理对象的字节码。
    • Callback:用于提供增强的代码
      • 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部须的。
      • 此接口的实现类都是谁用谁写。
      • 我们一般写的都是该接口的子接口实现类:MethodInterceptor
public static void main(String[] args) {
    final Producer producer = new Producer();
    Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
        /**
        * 执行北地阿里对象的任何方法都会经过该方法
        * @param proxy
        * @param method
        * @param args
        * 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
        * @param methodProxy 当前执行方法的代理对象
        */
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            //提供增强的代码
            Object returnValue = null;

            //1.获取方法执行的参数
            Float money = (Float)args[0];
            //2.判断当前方法是不是销售
            if("saleProduct".equals(method.getName())) {
                returnValue = method.invoke(producer, money*0.8f);
            }
            return returnValue;
        }
    });
    cglibProducer.saleProduct(12000f);
}

2. AOP术语

  • Joinpoint(连接点):实现类中的方法
  • Pointcut(切入点):被增强的方法(拦截)
  • Advice(通知/增强):拦截后要做的事(增强被代理类的方法)
    • 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通
    • 通知的类型: 前置通知、后置通知、异常通知、最终通知、环绕通知(有明确的切入点方法调用)
  • Introduction(引介)了解:引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
  • Target(目标对象):代理的目标对象(被代理对象)
  • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程(就是增强被代理类中方法的过程叫织入),spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入
  • Aspect(切面):是切入点和通知的结合

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-aopartifactId>
    <version>5.2.3.RELEASEversion>
dependency>

<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjweaverartifactId>
    <version>1.9.5version>
dependency>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd"
       xmlns:aop="http://www.springframework.org/schema/aop">

beans>

3. AOP配置

  1. 把通知Bean也交给spring来管理
  2. 使用aop:config标签表明开始AOP的配置
  3. 使用aop:aspect标签表明配置切面
    • id属性:是给切面提供一个唯一标识
    • ref属性:是指定通知类bean的Id。
  4. 在aop:aspect标签的内部使用对应标签来配置通知的类型,我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
    • aop:before:表示配置前置通知
    • method属性:用于指定通知类中哪个方法是前置通知
    • pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

3.1 切点表达式

  • 切点表达式写法(*为通配符)(方法参数通配符为…):
    execution([权限修饰] 返回值(*) 方法所在的类的全路径.类名.方法名(方法参数类型) 方法抛出异常类型)
    
  • 标准的表达式写法,权限修饰符可以省略:
    public void top.xxx.ServiceImpl.*()
    
  • 返回值可以使用通配符,表示任意返回值:
    * top.xxx.ServiceImpl.*()
    
  • 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*. :
    * *.*.*.*.ServiceImpl.*())
    
  • 包名可以使用…表示当前包及其子包:
    * *..ServiceImpl.*()
    
  • 类名和方法名都可以使用*来实现通配:
    * *..*.*()
    
    • 参数列表:
      • 可以直接写数据类型:基本类型直接写名称 int,引用类型写包名.类名的方式:java.lang.String
      • 可以使用*任意类型,参数不能为空
      • 可以使用“…”表示:0个或多个参数,且类型任意
      • 无参为()
  • 实际开发中切入点表达式的通常写法:切到业务层实现类下的所有方法:
    * top.tdte.service.*.*(..)
    

示例(约束省略):



<aop:config>
    
    
    <aop:pointcut id="pointcut" expression="execution(*  top.tdte.service.impl.AccountServiceImpl.*(..) ) "/>

    
    <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
aop:config>



<bean id="accountService" class="top.springaop.service.AccountServiceImpl"/>

<bean id="logger" class="top.springaop.utils.Logger"/>
<aop:config>
    <aop:aspect id="logAspect" ref="logger">
        <aop:before method="printLog"
                    pointcut="execution(* top.springaop.service.AccountServiceImpl.saveAccount())"/>
    aop:aspect>
aop:config>

4 通知分类


<aop:config>
    
    <aop:pointcut id="pt1" expression="execution(* top.tdte.service.impl.*.*(..))"/> 
    <aop:aspect id="logAdvice" ref="logger">
        
        <aop:before method="beforePrintLog" pointcut-ref="pt1" />

        
       <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/>
        
        
        <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1" />

        
        <aop:after method="afterPrintLog" pointcut-ref="pt1" />

        
        <aop:around method="aroundPringLog" pointcut-ref="pt1" />
        
            <aop:pointcut id="pt1" expression="execution(* top.tdte.service.impl.*.*(..))"/> 
    aop:aspect>
aop:config>

4.1 环绕通知

  • 环绕通知需要显示的配置一个参数ProceedingJoinPoint 显示的调用执行切入点方法。
    • Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
  • spring中的环绕通知:它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。

Log通知类配置中的成员方法:

public Object aroundPringLog(ProceedingJoinPoint pjp){
    Object rtValue = null;
    try{
        Object[] args = pjp.getArgs();//得到方法执行所需的参数
        System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
        rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
        System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
        return rtValue;
    }catch (Throwable t){
        System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
        throw new RuntimeException(t);
    }finally {
        System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
    }
}

5. 注解实现AOP

  • 注解与配置文件配置AOP相比,顺序会出现改变,注解中的最终通知先执行与后置通知调
    • @after 先执行,再执行@AfterReturning或@AfterThrowing
    • 顺序问题解决方法:改用环绕通知

使用注解前提:


<aop:aspectj-autoproxy/>


或者在配置类的类注解上添加注解
@EnableAspectJAutoProxy
开启aop注解支持

通知类示例:

//配置文件开启注解扫描
//把本类交给spring来管理,否则不能切入,或配置bean也可以
@Component 
//表示当前类是一个切面类
@Aspect 
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}

	// 前置通知
	@Before("pt1()")
    public  void beforePrintLog(){
        System.out.println("beforePrintLog方法开始记录日志了。。。");
    }

    // 后置通知
	@AfterReturning("pt1()")
    public  void afterReturningPrintLog(){
        System.out.println("afterReturningPrintLog方法开始记录日志了。。。");
    }
    
    // 异常通知
	@AfterThrowing("pt1()")
    public  void afterThrowingPrintLog(){
        System.out.println("afterThrowingPrintLog方法开始记录日志了。。。");
    }

    // 最终通知
	@After("pt1()")
    public  void afterPrintLog(){
        System.out.println("afterPrintLog方法开始记录日志了。。。");
    }

    // 环绕通知
    @Around("pt1()")
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("aroundPringLog方法开始记录日志了。。。最终");
        }
    }
}

四、Spring JDBC

1. Spring内置数据源

DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.cj.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/xxx");
ds.setUsername("");
ds.setPassword("");

2. JdbcTemplate

  • JdbcTemplate是spring提供的,轻量封装jdbc的工具类,

JdbcTemplate中crud的简单使用:

public class JdbcTemplateDemo3 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jt = ac.getBean("jdbcTemplate", JdbcTemplate.class);

        // 保存
        jt.update("insert into account(name,money)values(?,?)", "eee", 3333f);
        // 更新
        jt.update("update account set name=?,money=? where id=?", "test", 4567, 7);
        // 删除
        jt.update("delete from account where id=?", 8);
        // 查询所有
        // List accounts = jt.query("select * from account where money > ?",new AccountRowMapper(),1000f);
        List<Account> accounts = jt.query("select * from account where money > ?", new BeanPropertyRowMapper<Account>(Account.class), 1000f);
        for (Account account : accounts) {
            System.out.println(account);
        }
        // 查询一个
        List<Account> accounts1 = jt.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), 1);
        System.out.println(accounts1.isEmpty() ? "没有内容" : accounts1.get(0));

        //查询返回一行一列(使用聚合函数,但不加group by子句)
        Long count = jt.queryForObject("select count(*) from account where money > ?", Long.class, 1000f);
        System.out.println(count);
    }
}

//定义Account的封装策略
class AccountRowMapper implements RowMapper<Account> {
    /**
     * 把结果集中的数据封装到Account中,然后由spring把每个Account加到集合中
     */
    @Override
    public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
        Account account = new Account();
        account.setId(rs.getInt("id"));
        account.setName(rs.getString("name"));
        account.setMoney(rs.getFloat("money"));
        return account;
    }
}

3. JdbcDaoSupport

​ 由于dao层不同的实现类都需要显示的配置JdbcTemplate对象,为了简化代码,spring提供了一个类JdbcDaoSupport,他是 spring 框架为我们提供的一个类,该类中定义了一个 JdbcTemplate 对象,我们可以直接在dao的实现类继承JdbcDaoSupport,在实现类中实用getJdbcTemplate()方法来获取JdbcTemplate对象

​ 由于JdbcDaoSupport是spring提供的,因此不能使用注解注入,只能通过配置类或配置xml文件进行对象的注入

  • 注入方式:通过子类直接注入数据源即可
// 先给JdbcDaoSupport注入数据源,再使用
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
    @Override
    public Account findAccountById(Integer accountId) {
        List<Account> accounts = super.getJdbcTemplate().query("···");
        return accounts.isEmpty()?null:accounts.get(0);
    }
}

2. Spring 声明式事务

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-txartifactId>
    <version>5.2.5.RELEASEversion>
dependency>
<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjweaverartifactId>
    <version>1.9.5version>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.20version>
dependency>
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-jdbcartifactId>
    <version>5.2.5.RELEASEversion>
dependency>

配置文件约束:


<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"
       xmlns:tx="http://www.springframework.org/schema/tx"
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/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">
beans>  

2.1 配置文件

spring中基于XML的声明式事务控制配置步骤

  1. 配置事务管理器
  2. 配置事务的通知
    • 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
    • 使用tx:advice标签配置事务通知,属性:
      • id:给事务通知起一个唯一标识
      • transaction-manager:给事务通知提供一个事务管理器引用
  3. 配置AOP中的通用切入点表达式
  4. 建立事务通知和切入点表达式的对应关系
  5. 配置事务的属性:是在事务的通知tx:advice标签的内部

在第5点中配置事务的属性说明(tx:attributes标签下tx:method标签的属性):

  • isolation(隔离):用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
  • propagation(传播):用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
  • read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
  • timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
  • rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
  • no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
<beans>
    
    
    
    <bean id="accountDao" class="...."/>
    
    <bean id="dataSource" class="...."/>

    
    <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="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/> 
        tx:attributes>
    tx:advice>

    
    <aop:config>
        
        <aop:pointcut id="pt1" expression="execution(* top.tdte.service.impl.*.*(..))">aop:pointcut>
        
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
    aop:config>
beans>

2.2 半注解配置

配置文件:

<beans>
    <context:component-scan base-package="top.tdte"/>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    bean>

    
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://tdte.top:3306/db1"/>
        <property name="username" value="tdte"/>
        <property name="password" value="311776"/>
    bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>
    
	
    <tx:annotation-driven transaction-manager="transactionManager"/>
beans>

配置注解:

@Service("accountService")
//因为注解有默认属性,只要有@Transactional就会控制住事务问题
//也可以自定义配置,如果在类上就是全局配置,作用于整个方法
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private AccountDao accountDao;

    @Override
    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);
    }

    // 或者在方法上配置,作用域指定的方法
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
        //2.1根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.2根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //2.3转出账户减钱
        source.setMoney(source.getMoney() - money);
        //2.4转入账户加钱
        target.setMoney(target.getMoney() + money);
        //2.5更新转出账户
        accountDao.updateAccount(source);
        //int i = 1 / 0;
        //2.6更新转入账户
        accountDao.updateAccount(target);
    }
}

2.3 全注解

@Configuration
@ComponentScan("top")
@PropertySource("db.properties")
//开启声明式事务注解
@EnableTransactionManagement
public class SpringConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    
	// 给dao层继承JdbcDaoSupport父类注入数据源
    @Bean("accountDao")
    IAccountDao template(DataSource dataSource) {
        AccountDaoImpl accountDao = new AccountDaoImpl();
        accountDao.setDataSource(dataSource);
        return accountDao;
    }
	// 数据源
    @Bean("dataSource")
    DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
    // 事务管理器
    @Bean("tx")
    PlatformTransactionManager transactionManager(DataSource dataSource)  {
        return new DataSourceTransactionManager(dataSource);
    }
}
// 业务层声明式事务
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;
    
    @Autowired
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    
    @Override
    @Transactional
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
        //2.1根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.2根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //2.3转出账户减钱
        source.setMoney(source.getMoney() - money);
        //2.4转入账户加钱
        target.setMoney(target.getMoney() + money);
        //2.5更新转出账户
        accountDao.updateAccount(source);

        int i = 1 / 0;

        //2.6更新转入账户
        accountDao.updateAccount(target);
    }
}

MyBatis-Spring整合的事务控制详情请看官方文档

你可能感兴趣的:(Web)