Spring 学习笔记

 

 

目录

一、几个概念

二、Spring 简介

 三、搭建 Spring 环境

四、使用 Spring 开发

五、Spring 中的工厂

六、配置文件详解

七、IOC 控制反转(值注入和类型注入)

八、设计模式 -- 桥接模式

九、构造器注入

十、集合注入

十一、IoC 的注解方式

1. @Componant :

2.注入值的注解

十二、AOP简介

1. AOP 的概述

2.AOP 的底层原理 -- 代理模式(了解)

2.1 JDK 动态代理

2.2 CGLIB 动态代理

3. AOP 的术语

4. AOP 的简单使用

5. AOP 的切入表达式

6.AOP 的通知类型。

十三、AOP 的注解方式

十四、Spring 的 JDBC 模板

十五、使用 Spring 整合第三方数据源

十六、JDBC 模板的使用

十七、事务

 

附:关于 MySQL 8 的使用


一、几个概念

 

XML  : 用来传输数据,因为解析数据太过繁琐,后来就使用 JSON 来替代。那么现在大家都是使用 XML 来做配置文件。

IOC : 控制反转(外层):把你的权限交给 Spring 去管理

           (小时候吃饭,吃饭的权限交给老妈管理)

DI : 依赖注入(内层):Spring 会把你想要的东西直接给你用。

          (小时候打针,护士小姐姐会根据你哪里不舒服,就把对应的药,打到哪里)

AOP : 面向切面:就是用来对某个功能进行迭代增强的。

二、Spring 简介

1.Spring 是一个开源的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。

也就是 Spring 建议我们在编程的时候,使用面向接口的方式。

我们一般情况,Service 和 Dao 都是需要执行面向接口编程的。

接口类+实现类(多个)。

因为我们可以定义一个接口,把规范放出来,调用者只需要了解规范即可不需要了解我们的实现细节。

一个接口还可以有多个实现,这样可以很好的进行扩展。

在我们后期,如果想要增加或者改变功能,只需要制定对应的实现类即可,不需要关心前面的人怎么去调用。

就是需要交个 Spring 管理。

2.Spring 也是一个 JavaEE(JakartaEE)轻量级企业应用的一个开源框架,主要是为了更好的解耦合。

  • 轻量级:指耦合度比较低的应用。
  • 重量级:指耦合度比较高的应用。

容器 = 上下文:

相当于,我们画画的时候,需要在画板上才能画,画板外是不能画的。

就是一个我们操作的范围,在某个指定的范围内,我们能够去操作对应的数据。

3.Spring 的特点

1)方便解耦,简化开发

2)AOP 面向切面变成的支持

3)声明式事务的支持:

     事务都需要在 Service 层实现,开启事务、提交事务、回滚数据。

    使用 Spring 之后,就 so easy , 所有的事务操作, Spring 会自动完成。

    ①开启事务注解

    ②在 Service 层添加事务注解

4)方便程序的测试:整合 Junit 测试框架

5)方便继承各种优秀框架:Hibernate、MyBatis、Dubbo、Zookeeper、Struts2、...

6)降低 Java EE API 的使用难度

7)Java 源码是经典学习范例:里面包含了大量优秀的设计模式,代码确实很优雅。

Spring 是一个超级大的家族,几乎包含了你开发中每个层面的技术框架。

 三、搭建 Spring 环境

我们需要搭建 Spring 开发环境的话,这里直接采用导入 jar 包到工程中,进行开发。

1.获取 Spring 的开发资源

官网:http://spring.io/ 不能下载 jar 文件,只能用 maven 或者 gradle 来构建。

下载 jar 包:http://repo.springsource.org/libs-release-local/org/springframework/spring/

点进对应的版本,即可看到对应的资源。

下载可以得到一个压缩包,解压之后的目录如下:

需要用到的 jar 包一共有 21 个。

API:就是人家写好的代码,放了一个接口出来,你如果想要用的话,直接调用的这个东西就是 API。就好比,你做一个插座,人家拿一个插头过来怼进来就可以有电用了。

2.新建 Web 动态工程 - Spring01,并添加相关的目录结构。

在创建目录结构的时候,没有特别要求的话,一般使用公司域名反写。

action:主要存放表现层的代码,不如 struts2 和 springMVC 的代码

bean:主要存放 JavaBean 代码,主要是用来做数据映射和数据封装的。

commons:一些常用的类

dao:主要存放持久层代码,包括接口类和实现类。

          其中实现类,需要额外放到 impl 包中。

          比如 Hibernate 和 Mybatis 的代码

service:主要是存放业务逻辑层代码,包括接口类和实现类。其中实现类,需要额外放到 impl 包中。

test:可以放一些测试用的代码。

utils:可以放功能封装好的工具类,比如 JSON 的解析、数据库的连接、验证码生成等。

 

另外,新建一个名为 config 的source folder 文件夹,用来存储一些配置文件。

❤ 其中注意: source folder 是虚拟文件夹,不在磁盘中显示的,主要文件是在类路径下的。folder 是物理文件夹,可以在磁盘中查找出来的。如果访问 folder 中的类,需要给定全路径。

3.导入 Spring 的 21 个 jar 包到项目的 /WebContent/WEB-INF/lib 包中,不需要手动 build path ,它会自动添加到开发资源库环境中。

4.手动在 /WebContent 下,添加一个 index.html 文件,因为 web.xml 文件中指定了一个欢迎页面,所以需要有一个对应的文件,否则会报 404 找不到资源异常。

四、使用 Spring 开发

1.回顾以前调用某类中的某方法

1)在 service 包中,创建 UserService 接口类和 UserServiceImpl 类。

 先在接口类中,声明一个方法规范。

package com.dong.service;

public interface UserService {
	public void sayHello() throws Exception;
}

然后在实现类中,进行重写实现。

package com.dong.service.impl;

import com.dong.service.UserService;

public class UserServiceImpl implements UserService{

	@Override
	public void sayHello() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("我是 UserServiceImpl 类...");
	}
}

2)新建一个 Demo 的测试类,直接通过使用 new 的方式来创建对象实例,并调用方法。

public class DemoTest {
	@Test
	public void fun1() throws Exception {

		// 在没有使用 Spring 之前,我们需要使用 new 关键创建对象实例
		UserService us = new UserServiceImpl();
		us.sayHello();
	}

}

❤ 使用 @Test 注解进行单元测试时,需要导入Junit4 关联包,就可以直接选中方法名,右键执行。

2.使用 Spring 方式来创建

1)配置 Spring 的配置文件,可以参考开发者文档中的模板。

在 config 文件夹中,新建一个 spring-beans.xml 文件,其中模板直接拷贝使用。

<?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>

2)在 spring-beans.xml 配置文件中,将 UserServiceImpl 类交给Spring 管理。

<!-- 将 UserServiceImpl 类,交给 Spring 管理 -->   	
<bean id="userService" class="com.dong.service.impl.UserServiceImpl"></bean>

3)回到 DemoTest 类中,通过三个步骤,读取文件,获取对象实例,执行方法。

@Test
public void fun2() throws Exception {
    // 1. 读取 beans.xml 配置文件,获取 Spring 的上下文
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
    // 2. 获取 UserServiceImpl 实例
    UserService us = (UserServiceImpl) context.getBean("userService");
    // 3. 调用方法
    us.sayHello();
}

五、Spring 中的工厂

1.ClassPathXmlApplicationContext 类,通过源码观察,到最后其实它为了去获取某个资源,通过加载资源,最终得到一个 Spring 的上下文对象。

在 org.springframework.core 包的 ApplicationContext 接口中,可以看到包含 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 两个实现类。

ClassPathXmlApplicationContext : 主要是去类路径中,获取对应的资源文件,加载得到上下文对象。

FileSystemXmlApplicationContext : 主要是在本地磁盘中,获取对应的资源文件,加载得到上下文对象。

注意,以后不管写页面,或者是代码,如果需要获取某些资源,比如图片、配置文件等。一定要动态去获取“类资源路径”,也叫 classpath,也叫项目的根路径。可以通过一些专门使用反射原理的类来获取,只需要给名字即可,不需要硬性指定文件的路径。

另外,在页面的时候,直接通过 ${pageContext.request.contextPath}/请求名字或者页面名字。

2.ApplicationContext 其实是 BeanFactory 的子接口

BeanFactory 是一个工厂,通过名字,就可以看出,这个工厂主要是用来生产 Bean。

BeanFactory 底层使用的是延迟加载方式,第一次调用 getBean 方法时,才会初始化 指定的 Bean。

ApplicationContext 会在项目启动之后,当 beans.xml 文件被加载后,就可以直接使用指定的 bean了,省了每次都需要调用 getBean() 方法,才能获取到实例。

六、配置文件详解

在 spring-beans.xml 文件中,我们主要通过 <bean> 标签来管理对应的 Bean。

<bean id="userService" class="com.dong.service.impl.UserServiceImpl"></bean>

1. id 属性:给 bean 起一个名字,这个名字代表的就是对应的 Bean ,如果其他地方需要引用的话,直接把名字指定过去即可的。

2. name 属性:也是用来给 bean 起名字的,但没有唯一性,所以我们推荐大家使用 id 来配置。

3. class 属性:用来设置需要给 Spring 管理的 Bean 对应的路径。

4. scope 属性:用来指定范围的。

a)singleton 单例(默认值)在开发中,尽量少用单例,因为不安全,容易受其他地方修改,导致数据的不完整性和数据不安全。

b)prototype 多例,以后我们 Spring + Struts2 整合使用的时候,Action 类都需要设置为多例。

c)crequest 请求,每次 http 请求都会创建一个新的 Bean。

d)session 会话,同一个 session 会话共享一个 Bean。

e)gloableSession 全局会话

5. Bean 对象创建和销毁的时候自动执行对应的方法。

a)init-method:主要是在 Bean 创建的时候,自动执行,一般情况下,我们用于初始化某些数据,然后就可以直接使用。类似于程序中的一个静态块。

b)destory-method:主要是在 Bean 销毁的时候,比如服务器再正常关闭情况下就会销毁。一般情况下,我们会在销毁方法中释放掉某些资源,比如数据库连接、输入输出流等。

七、IOC 控制反转(值注入和类型注入)

控制反转主要有两种说法:IOC 控制反转、DI 依赖注入。

这两个概念是两个不同的人提出来的,其实质讲的还是用一个内容。

有些人会分开来说,是不同的东西,有些人,又说是同一样的东西。都不要理,如果确实要分开来讲的话,可以认为它们是同一个东西中的不同部分。

控制反转,还是一句话,将 bean 原有的创建、管理等权限交付给 Spring。

由 Spring 来控制它何时创建,给谁用。

1.值注入

    需要满足三个条件:

    1)添加字段,比如 name

    2)添加 set 方法,比如 setName

    3)在 spring-beans.xml 配置文件中,通过 <property> 属性来注入值

// 在 UserServiceImpl 中
// 1. 添加字段
private String name;
// 2. 添加 set 方法
public void setName(String name) {
    this.name = name;
}

@Override
public void sayHello() throws Exception {
    System.out.println("我是 UserServiceImpl 类..."+name);
}
// 3. 在 spring-beans.xml 文件中,添加 <property> 配置信息。
<bean id="userService" class="com.neuedu.service.impl.UserServiceImpl">
<!-- 给类中属性,设置值,也叫注入值:也就是哪里想要值,
        spring 就可以往哪里设置值 -->
<property name="name" value="翠花"></property>
</bean>

编写测试类: 

@Test
public void fun2() throws Exception {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
    UserService us = (UserService)context.getBean("userService");
    us.sayHello();
}

2.类型注入

public interface UserDao {
    public void save() throws Exception;
}
// 1. 在 dao 包中,创建 UserDao 接口类,然后添加 save() 方法
public void save() throws Exception;

// 2. 在 dao.impl 实现类包中,创建 UserDaoImpl 实现类,重写 save() 方法。
@Override
public void save() throws Exception {
    System.out.println("我是 UserDao 中的 save() 方法");
}
// 3. 回到 UserServiceImpl 实现类中,添加 userDao字段
private UserDao userDao;

// 4. 接着添加 set 方法
public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
}

@Override
public void sayHello() throws Exception {
    System.out.println("我是 UserServiceImpl 类..."+name);
    // 直接调用 userDao 里面的接口
    userDao.save();
}
// 5. 将 UserDaoImpl 交付给 Spring 去管理 
<bean id="userDao" class="com.neuedu.dao.impl.UserDaoImpl"></bean>

// 6. 需要将 userDao 引入到 userService 中。 
<bean id="userService" class="com.neuedu.service.impl.UserServiceImpl">
        …
<!-- 如果要配置类型注入的话,需要使用 ref属性来指定,
        代表 reference 关联哪个指定的类 -->
<property name="userDao" ref="userDao"></property>
</bean>

编写测试类:

@Test
public void fun2() throws Exception {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
    UserService us = (UserService)context.getBean("userService");
    us.sayHello();
}

 

八、设计模式 -- 桥接模式

如果一个组件存在 2 个或者 2 个以上维度(比如,选择)的变化。就应该将组件分离成 2 类或者 2 类以上的组件。

每类组件专门负责一个维度的变化。

最后才将这 2 类或者 2 类以上的组件组合起来得到一个最终的组件。比如,鸡肉 + 面 + 微辣 + 葱花 = 葱花微辣鸡肉面。

在开发中,我们的中间层组件,同样存在 service 业务层和 dao 持久层技术的两个维度的变化,应该将该组件分离成 2 类的组件:service 组件和 dao 组件。

其中,service 组件负责业务规则的改变,dao 组件负责持久层技术的改变。

当业务规则发生改变的时候,项目只需要去修改 service 组件即可。

当持久层技术发生改变的时候,项目只需要去修改 dao 组件即可。

九、构造器注入

构造器:主要用来在类启动的时候,进行实例化,可以获取到实例。

有参构造器和无参构造器的区别:就是你妈生你的时候和哪吒他妈生他的时候。

如果是直接将 car 交给 Spring 管理,这个时候,底层驱动 Spring 去执行的是当前类的无参构造器,然后去构建类的实例。

<!-- 将 Car 类,交给 Spring 管理-->
<bean id="car" class="com.neuedu.bean.Car"></bean> 
步骤:
1)	创建 Car 类。
public class Car implements Serializable {
    private String name;
    private double money;

    // 添加有参构造器	
    public Car(String name, double money) {
        super();
        this.name = name;
        this.money = money;
    }

    // toString() 是为了打印对象实例的时候,方便查看数据
    @Override
    public String toString() {
        return "Car [name=" + name + ", money=" + money + "]";
    }
} 
2)	把 Car 类交给 Spring 去管理,
然后通过使用 <constructor-arg> 标签给有参构造器中的参数进行传值
有多少个参数,就需要有多少个 <constructor-arg> 标签。

<!-- 将 Car 类,交给 Spring 管理-->
<bean id="car" class="com.neuedu.bean.Car">
    <!-- 给有参构造器中的参数进行赋值 -->
    <constructor-arg name="name" value="宝马X6"/>
    <constructor-arg name="money" value="120"/>
</bean>
3)	测试
@Test
public void fun4() throws Exception {
    ClassPathXmlApplicationContext context =
            new ClassPathXmlApplicationContext("spring-beans.xml");
    Car car = (Car)context.getBean("car");
    System.out.println(car);
}
如果说,这个车是属于某个人的呢?
	
步骤:
1)	创建 User 类,然后引入 Car 类,并创建有参构造器。
public class User implements Serializable {
    private String name;
    // 如果说,某个人拥有一辆车
    private Car car;
    // 使用构造器的方式来注入
    public User(String name, Car car) {
        super();
        this.name = name;
        this.car = car;
    }
    @Override
    public String toString() {
        return "User [name=" + name + ", car=" + car + "]";
    }
}
2)	在 beans.xml 文件中,将 User 交付给 Spring 管理。
<!-- 将 User 类,交给 Spring 管理-->
<bean id="user" class="com.neuedu.bean.User">
    <constructor-arg name="name" value="翠花"/>
    <constructor-arg name="car" ref="car"/>
</bean>
3)	测试
@Test
public void fun5() throws Exception {
    ClassPathXmlApplicationContext context = 
            new ClassPathXmlApplicationContext("spring-beans.xml");
    User user = (User)context.getBean("user");
    System.out.println(user);
}

 

十、集合注入

常见的集合:array、list、set、map、properties

注意:此处的 array 和 list 是一样的。

1. array 

// 在 User.java 中添加一个数组
// 数组
private String[] strs;
public void setStrs(String[] strs) {
    this.strs = strs;
}
<!-- 把 User 交个 Spring 管理 -->
<bean id="user" class="com.tt.bean.User">
    <constructor-arg name="name" value="zheng" />
    <constructor-arg name="car" ref="car" />
    <property name="strs">
        <array>
            <value>花花</value>
            <value>画画</value>
        </array>
    </property>
</bean>
@Test
public void fun5() throws Exception {
    ClassPathXmlApplicationContext context = 
            new ClassPathXmlApplicationContext("spring-beans.xml");
    User user = (User)context.getBean("user");
    System.out.println(user);
}

2. list :跟上面使用的 array 是一样的效果。

3. set:

1)先在 User 类中,添加字段和 set 方法
// 特点:没有顺序,不重复
private Set sets;
	
public void setSets(Set sets) {
	this.sets = sets;
}

2)在 beans.xml 文件中设置。
<bean id="user" class="com.neuedu.bean.User">
    <property name="sets">
        <set>
            <value>翠花</value>
            <value>春花</value>
            <value>牵牛花</value>
            <value>龙眼花</value>
        </set>
    </property>
</bean>

4. map:主要是以 key-value 形势来显示。以后学习的 JSON 也是 key:value 形势。

1)	先在 User 类中,添加 map 字段
// map
private Map<String, String> map;
public void setMap(Map<String, String> map) {
	this.map = map;
}

2)	在 beans.xml 文件中配置
<!-- map -->
<bean id="user" class="com.neuedu.bean.User">
    <property name="map">
        <map>
            <entry key="8" value="翠花"></entry>
            <entry key="9" value="春花"></entry>
            <entry key="7" value="小翠"></entry>
        </map>
    </property>
</bean>

 5. Properties key = value key-value key:value

1)在 User 中添加字段

2)在 spring-beans.xml 文件中配置

十一、IoC 的注解方式

1. @Componant :

用来配置 Bean ,Spring 认为 Bean 就是所有的 Java 类。如果你希望当前类需要驱动 Spring 去管理,则需要使用到此注解。

此注解相当于 <bean id="" class="" />

如果说,我当前的类是一个 Service 层或者 Dao 层,又或者是 Web 层的类?

@Controller 来表示当前类是表现层的。

@Service 来表示当前类是服务层的。

@Repository 来表示当前类是持久层的。

注意:上面的这三个注解,只是让被标注的类用途清晰而已。

2.注入值的注解

当我们使用注解的方式来注入值的时候,就不需要再添加 setXxx() 方法了。

1)注入普通类型的值:@Value
	@Value(value="翠花")
	private String name;
spring-beans.xml

<?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: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="com.tt"/>
    
</beans>
// 把原来的 UserServiceImpl 的内容给删除掉,换成下面的内容

// @Component 相当于配置文件中的 bean 
@Component(value="userService")
public class UserServiceImpl implements UserService{
    //----- 值注入 ------
    @Value(value="zheng")
    private String name;

    @Override
    public void sayHello() throws Exception {
        System.out.println("我是 UserServiceImpl 类..."+name);
    }
}

 编写测试类:

// 加载配置文件
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-beans.xml")
public class Demo {
	// 相当于获取 bean 的 id 值
    // name 的值要于 @Component 的 value 值一样
    @Resource(name="userService")
    private UserService userService;
	
    @Test
    public void fun1() throws Exception {
        userService.sayHello();
    }
}
2)注入对象类型的值:@Autowired
@Autowired 自动装配
// 因为一个接口,可以有多个实现类
// 如果需要执行指定的实现类,则需要强制使用名称注入方式
@Qualifier(value="userDao2")

// 上面如果强制指定的话,需要分两步来实现,太麻烦
// 直接使用 Resource 指定即可
@Resource(name="userDao2")
// 直接使用 @Resource 也是可以的,
// 默认会执行名字跟字段一致的实现类
@Resource
private UserDao userDao2;

// 在实际开发中,我们很少会为一个接口,添加过多的实现类
// 一般情况,我们都是一个接口对应着一个实现类
// 因为开发的时候,太多东西放在一个接口的时候,会很混乱
// 在 UserDaoImpl.java 里面打上一个 @Component 的注解

@Component(value="userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void save() throws Exception {
        // TODO Auto-generated method stub
        System.out.println("我是UserDao中的save()方法");
    }
}

然后在 UserServiceImpl.java 里面注入,然后直接调用。

运行注入普通类型的测试类即可。

3. Bean 的作用范围

@Scope(value = "prototype") 设置多例,所有的 action 类都需要设置为多例。

4. 初始化和销毁

@PostConstract  相当于  init-method

@PreDestory  相当于  destory-method

 

十二、AOP简介

1. AOP 的概述

        如果我们要增强功能或者迭代新版本的话,在 OOP 中只能通过集成类和实现接口的方式来实现,但会使代码的耦合度增强,并且继承类只能为单继承,这样会阻碍更多行为添加到一组类中,AOP 一定程度上,弥补了 OOP 的不足,也是 OOP 的一种延续。

        AOP 是面向切面编程,Aspect Oriented Programming.

        AOP 是由 AOP 联盟组织提出来的一套编程规范, Spirng 只是将它引入到自身框架中使用,使用的时候,必须遵守 AOP 联盟的规范。(比如,我们在使用 Spring 切面的时候,需要导入联盟包中的某些 jar 文件)

        AOP 是 Spring 框架中的一个非常重要的内容,必须要掌握的。也是函数式编程的一种衍生框架。

       还可以利用 AOP 对业务逻辑层的各个部分进行隔离,主要是为了降低耦合度。

 

2.AOP 的底层原理 -- 代理模式(了解)

        如果需要访问目标对象,必须通过代理的处理,当处理完了之后,再去访问目标对象获取对应的数据。

AOP 的底层代理实现,主要是有两种方式:

1)基于 JDK 的动态代理

      必须是面向接口的,只有实现了具体的接口,才能成为代理对象。

2)基于 CGLIB 的动态代理

     对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式。

      在 Spring 的 core 包中,可以看到对应的源码。

2.1 JDK 动态代理

1)添加 UserDao 接口和 UserDaoImpl 实现类。

public interface UserDao {
    public void save() throws Exception;
    public void update() throws Exception;
}

public class UserDaoImpl implements UserDao {
    @Override
    public void save() throws Exception {
        System.out.println("我是 save() 方法");
    }
    @Override
    public void update() throws Exception {
        System.out.println("我是 update() 方法");
    }
}

2)新建 MyJDKProxy 代理类。

public class MyJDKProxy {
    // 思路:先把需要代理增强的东西传进来,接着增强之后再把新的返回出去
    public static UserDao getProxy(final UserDao userDao) {
        // 获取类加载器
        ClassLoader loader = userDao.getClass().getClassLoader();
        // 获取接口
        Class<?>[] interfaces = userDao.getClass().getInterfaces();
        // 代理的实现
        // 后面的回调函数,执行得到的结果,
        // 当做参数给 newProxyInstance() 方法使用
        UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(loader, 
                interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, 
                                 Object[] args) throws Throwable {
                // 把需要增强的内容,在此处执行
                if (method.getName().equals("save")) {
                    System.out.println("我要增强了,我准备 12 块腹肌了...");
                }
                // 把增强的内容,进行返回
                return method.invoke(userDao, args);
            }
        });
        // 接着,把代理继续返回
        return userDaoProxy;
    }
}

3)测试类

@Test
public void fun1() throws Exception {
    UserDao userDao = new UserDaoImpl();
    // 使用 JDK 动态代理
    UserDao userDaoProxy = MyJDKProxy.getProxy(userDao);
    userDaoProxy.save();
}

2.2 CGLIB 动态代理

1)新建 AppleDao 接口和 AppleDaoImpl 实现类。(参考 UserDao 和 UserDapImpl)

2)新建 MyCGLibProxy 代理类。

public class MyCGLibProxy {
    public static AppleDaoImpl getProxy() {
        // 1. 创建 CGLib 核心类
        Enhancer enhancer = new Enhancer();
        // 2. 指定需要代理的类,也就是父类
        enhancer.setSuperclass(AppleDaoImpl.class);
        // 3. 设置回调函数
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args,
                                    MethodProxy methodProxy) throws Throwable {
                // 需要增强的内容写到此处	
                if(method.getName().equals("save")) {
                    System.out.println("我是增强后的 save() 方法");
                }
                // 返回执行
                // 会出现一个很诡异的事情,会出现死循环
                // return methodProxy.invoke(obj, args);
                return methodProxy.invokeSuper(obj, args);
            }
        });
        // 4. 生成代理对象
        AppleDaoImpl appleDaoProxy = (AppleDaoImpl) enhancer.create();
        return appleDaoProxy;
    }
}

3)测试类

@Test
public void fun2() throws Exception {
    AppleDao userDao = new AppleDaoImpl();
    // 使用 CGLib 动态代理
    AppleDaoImpl appleDapProxy = MyCGLibProxy.getProxy();
    appleDapProxy.save();
}

 

3. AOP 的术语

1)连接点 joinpoint

      指到是所有的方法,也是所有的拦截点。

2)切入点 pointcut 

      主要指的是,我们需要增强的一个或者多个指定的方法。与上面的连接点有点相同,注意概念上的区分。

3)通知、增强 advice

      简单理解,我们需要增强的那个方法所对应要做的事情,就是通知。

      通知的类型有多种:

              前置通知:before

              后置通知:after-returning

              最终通知:after

              环绕通知:around

              异常通知:exception

4)引介 introduction

在不修改类代码的前提下,一种特殊的通知。

它可以在运行期为类动态添加一些方法或者 filed 字段。

5)目标对象 target 

主要指的是我们想要代理的对象,也叫目标对象。

6)织入 weaving

 指的是就是将增强的内容应用到目标对象中,然后创建新的代理对象,这个过程就是织入。注意,这是一个动态的过程。

7)代理 proxy 

一个目标对象,被 AOP 织入增强之后,就会产生一个结果代理类。

8)切面 aspect = 切入点 pointcut + 通知 advice

 

4. AOP 的简单使用

1)导入 AOP 联盟包

2)配置 spring-beans.xml 文件

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

3)添加 UserDao 接口类和 UserDaoImpl 实现类(如果以前有,则无需再添加)

4)在 Demo 测试类中,注入 userDao,准备好等下可以使用。

5)添加 MyAspect 用于增强功能的切面类。

@Component(value = "myAspect")
public class MyAspect {
    public void hello() {
        System.out.println("我是增强后的内容..");
    }
}

6)回到 spring-beans.xml 文件中,配置 sop

<!-- 配置 AOP -->
<aop:config>
    <!-- 配置切面 = 切入点 + 通知 -->
    <aop:aspect ref="myAspect">
    <!-- 配置切入点
        expression 表达式
        execution(想要切的方法写到这里)
        execution(访问权限 返回值类型 包路径.方法名(参数))
        -->
    <aop:before method="hello" 
        pointcut="execution(public void com.neuedu.dao.impl.UserDaoImpl.save())"/>
    </aop:aspect>
</aop:config>

 

5. AOP 的切入表达式

切入点的书写格式:

1)基本的:execlution(访问权限 返回值类型 包名.类名.方法名(参数列表))

2)访问权限可以不写,属于不是必要的选项。

3)返回值类型,不能不写。但如果觉得太麻烦,则可以使用 * 替代。

4)包名,如果太长,我们可以省略某些部分。

      a)首先,起始包路径是不能省略的,比如 com ,但可以使用 * 替代。

      b)中间的包名,就可以省略了,可以使用 * 替代。

      c)如果中间隔着多个包的话,都想一起省略,可以使用 .. 符号。

5)类名,不能省略,但是可以使用 * 替代。

6)方法名,不能省略,但是可以使用 *替代。

7)关于参数,如果是一个参数的话,则可以使用 * 替代,如果有多个参数的话,可以使用 .. 替代。

注意:在使用的时候,最好不要全部用 * 替代所有的东西,最终会搞到 aop 乱切。

 

6.AOP 的通知类型。

AOP 的常见通知类型如下:

        1)前置通知:before

              在方法执行之前,进行通知。

        2)后置通知:after-returning

              在方法执行之后,进行通知,如果有异常,不会执行。

        3)最终通知:after

              在目标对象中的方法执行完之后才会执行,如果程序在运行的时候出现了异常,最终通知也会执行。

         4)环绕通知:around

              可以在方法执行的前后进行通知。呈包围状态。

              如果是直接使用的话,则只会执行增强的效果。如果还需要执行目标对象中的方法,还需要我们额外手动设置让方法可以执行。

          5)异常通知:exception

十三、AOP 的注解方式

1.定义切面类

        @Aspect 用来定义当前类是切面类。

2.定义通知类型

        @Before

        @AfterReturing

        @Around

        @After

        @AfterThrowing

3.步骤

1)新建 MyAspect 切面类,然后在切面类上面,使用 @Aspect 注解标注当前类是切面类。

@Component(value = "myAspect")
@Aspect
public class MyAspect {
}

2)新建 UserDao 接口类和 UserDaoImpl 实现类。并添加 save 和 update 方法。

@Override
public void save() throws Exception {
	System.out.println("我是 save() 方法");
}
	
@Override
public void update() throws Exception {
	System.out.println("我是 update() 方法");
}

3)在切面类中,添加对应的增强方法。

// 作为前置通知,切入到某个指定的方法中,进行增强
@Before(value = "execution(public void com.neuedu.dao.impl.UserDaoImpl.save())")
public void hello() {
    System.out.println("我是增强后的内容..");
}

@Around(value = "execution(public void com.neuedu.dao.impl.UserDaoImpl.update())")
public void arround(ProceedingJoinPoint joinpoint) {
    System.out.println("环绕通知 ----- 前面执行");
    try {
        // 在中间,需要把目标对象的方法也执行一遍
        joinpoint.proceed();
    } catch (Throwable e) {
        e.printStackTrace();
    }
    System.out.println("环绕通知 ----- 后面执行");
}

4.如果切入表达式,大部分都是一样的话,则可考虑抽取处理啊,简单利用

1)先抽取出来

@Pointcut(value = "execution(public void com.neuedu.dao.impl.UserDaoImpl.save())")
public void userSave() {}
	
@Pointcut(value = "execution(public void com.neuedu.dao.impl.UserDaoImpl.update())")
public void userUpdate() {}

2)进行使用:类名.方法名()

@Before(value = "MyAspect.userSave()")

@Around(value = "MyAspect.userUpdate()")

十四、Spring 的 JDBC 模板

1.简单理解 JDBC 流程

2.在数据库中,创建一张表

Create table stu (
    Id int primary key auto_increment,
    Name varchar(50),
    Age int
);

3.给项目导包,搭建环境。

1)启动包  mysql-connector-java-5.1.43-bin.jar

2)Spring 中所有相关的包:spring-jdbc、spring-tx

4.添加一个配置文件:db.properties,主要用来存储数据库连接的信息。

5.测试

public void fun1() throws Exception {

    // 1. 创建数据源对象
    DriverManagerDataSource ds = new DriverManagerDataSource();
    // 数据库的驱动
    ds.setDriverClassName("com.mysql.jdbc.Driver");
    // 连接数据库的地址
    // 如果数据库是本地的话,则可以简写为:jdbc:mysql:///jss
    ds.setUrl("jdbc:mysql://127.0.0.1:33066/jss?serverTimezone=UTC");
    // 用户名
    ds.setUsername("root");
    // 密码
    ds.setPassword("1234");

    // 2. 创建模板类
    JdbcTemplate template = new JdbcTemplate(ds);

    // 3. 完成数据的添加
    template.update("insert into stu values(1, 'cuihua', 18.8)");
}

但是,我们现在学了 Spring 之后,就不再建议大家通过 new 的方式去管理了。我们应该将所有的创建都交给 Spring 去管理。接着,我们使用 Spring 来处理数据源和模板的创建和使用。

6.编辑 spring-beans.xml 文件,添加数据源相关信息。

<!-- 将连接数据库的动作,交给 Spring 去管理 
    	以后,我们只要是配置数据源,都统一给 id 值,设置为 dataSource
    -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://127.0.0.1:33066/jss?serverTimezone=UTC"/>
    <property name="username" value="root"/>
    <property name="password" value="1234"/>
</bean>

7.编辑 spring-beans.xml 文件,添加模板相关信息。

<!-- 将 JdbcTemplate 模板交给 Spring 管理 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

8.测试

因为我们需要使用 template 模板,则需要通过注入的方式获取。

// 注入
@Resource
private JdbcTemplate template;

@Test
public void fun2() throws Exception {
    template.update("insert into stu values(null, ?, ?)", "花花", 1000);
}

十五、使用 Spring 整合第三方数据源

其实,就跟我们在家吃饭用的碗一样,不是吃完就丢掉。而是,吃完之后洗干净消毒,然后下顿饭再拿来用。

接着,主要介绍两个比较优秀的第三方的数据源工具,这两个东西是人家把功能封装好的小框架,可以直接添加到项目中进行使用的。在使用的时候,一样是需要设置一些数据库的基本信息,然后还可以设置一些数据池中的相关量。

比如,我们学习的Hibernate 持久层框架,底层用到的数据源就是 C3P0.

1.DBCP

记得添加下面两个 jar 包:

commons-dbcp2-2.5.0-bin.zip

commons-pool2-2.6.0-bin.zip

<!-- 配置 DBCP 数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://127.0.0.1:33066/jss?serverTimezone=UTC"/>
    <property name="username" value="root"/>
    <property name="password" value="1234"/>
</bean>

2.C3P0

需要添加一个 jar 包:c3p0

官网:https://www.mchange.com/projects/c3p0/ 

需要多导入 mchange-commons-java-0.2.3.4.jar 文件一起使用

注意:如果 c3p0 是跟 Spring 项目一期使用的话,则需要选择 springsource 资源中的版本。

<!-- 配置 C3P0 数据源  -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:33066/jss?serverTimezone=UTC"/>
    <property name="user" value="root"/>
    <property name="password" value="1234"/>
</bean>

十六、JDBC 模板的使用

1.修改

template.update("update stu set name = ? where id = ?", "春花", 1);

2.删除

template.update("delete from stu where id = ?", 5);

3.查询一条记录

User user = template.queryForObject(
        "select * from stu where id = ?",
        new BeanMapper(), 1);
System.out.println(user);

4.查询所有记录

// 方式2:查询所有的数据
List<User> list2 = template.query("select * from stu", new BeanMapper());
for (User user : list2) {
	System.out.println(user);
}
		
// 方式1: 查询所有的数据
List<Map<String, Object>> list = template.queryForList("select * from stu");
for (Map<String, Object> map : list) {
    //  map.keySet() 用于获取所有的 key
    for (String key : map.keySet()) {
        // 通过 key 获取到对应的 value 值
        System.out.print(map.get(key));
    }
    // 相当于换行效果
    System.out.println();
}

十七、事务

只要记住一句话:执行意见事情,要么一期成功,要么一期不成功。

比如,转账。

A 给 B 转账 1000 块。

如果 B 收到,则 A 账户中应该少 1000。

如果 B 未收到,则 A 账户中不变。

在执行转账的时候,万一银行的系统出现故障,刚好转账进行到中段,此时只要不能完全成功,数据应该回滚到原始状态。简单理解,就是什么都没发生过。

在开发中,我们的事务处理,一般情况都建议在 Service 层中进行。

在操作事务的时候,以前的做法大致如下:

1)开启事务

2)执行事务

3)提交事务

4)如果有异常出现,则回滚事务,将数据复原

 

现在如果使用 Spring 来处理事务,其实非常简单。

1)配置事务管理器

 <!-- 配置事务管理器 transactionManager
    	需要跟数据源一起使用
     -->
<bean id="transactionManager" 
       class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

2)开启事务驱动注解

<tx:annotation-driven transaction-manager="transactionManager"/>

3)在需要使用事务的类上(也就是 service 层),使用 @Transactional 注解即可。

注意,@Transactional 注解既可以作用在类上,也可以在类中单独的方法上实现事务。

 

附:关于 MySQL 8 的使用

 

1.安装 MySQL 8 版本需要注意的问题

参考:https://blog.csdn.net/u012278016/article/details/80539205

 

2.MySQL8 使用需要使用最新版本的驱动。

参考:mysql-connector-java-8.0.11.jar

 

3.在设置 URL 的时候

参考:"jdbc:mysql://127.0.0.1:3306/jss?serverTimezone=UTC"

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值