Hello Spring

Spring概述

Spring是一个轻量级[^体积小,需要的jar包少]的 、开源的J2EE框架,可以解决企业开发应用软件的复杂性。

核心部分

Spring的两个核心部分:iocAOP

  • IOC:控制反转,将创建对象的过程交给Spring管理,而不是手动的使用new关键字来创建对象。
  • AOP:面向切面,在不修改源代码的情况下,实现对软件的功能增强。

特点:

  • 方便解耦、简化开发
  • 支持AOP
  • 方便测试程序
  • 方便和其他框架进行整合
  • 方便进行事务的操作
  • 降低API[^jdbc等]的操作难度

其中Beans、 Core、Context、Expression还有一个日志包Commons-loggin是必须的

对应的maven依赖如下

<!-- Beans包的依赖 -->
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.2.6.RELEASE</version>
</dependency>

<!-- Core包的依赖 -->
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.2.6.RELEASE</version>
</dependency>

<!-- Context包的依赖 -->
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.6.RELEASE</version>
</dependency>

<!-- Expression包的依赖 -->
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>5.2.6.RELEASE</version>
</dependency>

<!-- loggin日志包的依赖 -->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.1</version>
</dependency>

Spring管理方法-配置文件方法

基本步骤:

首先创建一个测试类User

public class User {
    public void add() {
        System.out.println("add ...");
    }
}

然后创建Spring的配置文件[^application.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">

    <!-- 配置对象创建 -->
    <!-- id:通过id获取对象;class:类的全路径 -->
    <bean id="User" class="com.wsd.spring.User" ></bean>
</beans>

测试:从Spring容器中提取User对象

@Test
public void ApplicationTest() {
    // 1、加载配置文件
    String path = "src/application.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(path);
    // 2、从容器中获取对象
    User user = applicationContext.getBean("User", User.class);
    // 3、测试对象方法
    user.add();
}

注意:配置文件方法默认调用无参构造方法来创建对象,当类没有提供无参构造时,会出现错误

基于配置文件方法注入属性(DI[^依赖注入])

  1. set方法注入
  2. 有参构造方法注入

set注入:

<bean id="user" class="com.wsd.spring.User" >
    <property name="name" value="Wsd"></property>
    <property name="age" value="20"></property>
</bean>

有参构造注入:

<bean id="user" class="com.wsd.spring.User" >
    <constructor-arg name="name" value="Wsd"></constructor-arg>
    <constructor-arg name="age" value="20"></constructor-arg>
</bean>

注意:注入空值时,需要使用特殊的标签

<property name="schoolName">
	<null></null>
</property>

IOC

将对象的创建和对象之间的调用过程,交给Spring进行管理,来达到降低耦合度的目的。

IOC的底层技术:

  • XML解析
  • 工厂模式
  • 反射技术

Spring实现IOC的两方式[^两种接口]

  • BeanFactory

    Spring内部使用的接口,一般不向开发人员提供使用

    在加载配置文件的时候,不会创建对象[懒汉式],只有在获取对象的时候[使用getBean的时候才创建对象]

  • ApplicationContext

    BeanFactory的子接口,功能更加强大,在读取配置文件的时候,将对象一次性创建完成[^饿汉式]

Application的两个主要的实现类

  • ClassPathXmlApplicationContext:以相对路径[^项目的src]来获取配置文件
  • FileSystemXmlApplicationContext:以绝对路径来获取配置文件

依赖注入

< 普通注入方法 >  Ctrl+鼠标单机进入


级联注入

bean的内部再创建一个bean

一个部门类

public class Dept {
    int no;
    String name;
    /** set方法及构造方法省略 **/
}

一个员工类,每名员工都对应着一个部门

public class Emp {
    String name;
    int age;
	// 员工
    Dept dept;
    /**set方法省略。。。**/
}

级联注入配置文件

<bean id="emp" class="com.wsd.spring.Emp">
    <property name="name" value="Wsd"></property>
    <property name="age" value="21"></property>
    <property name="dept" ref="dept"></property>
    <!-- 注意:这种写法需要对应的get方法 -->
    <property name="dept.name" value="技术部"></property>
    <property name="dept.no" value="11010"></property>
</bean>

<bean id="dept" class="com.wsd.spring.Dept"></bean>

注入集合类型

1、注入数组

2、注入List

3、注入Map

······

新建一个学生类,用来模拟集合类型的使用情况

public class Student {
    private String[] courseArray;
    private List<String> courseList;
    private Map<String, String> courseMap;
 	// 省略set方法
}

配置xml配置文件

<bean id="stu" class="com.wsd.spring.Student">
    <!-- 数组类型注入 -->
    <property name="courseArray">
        <array>
            <value>Java</value>
            <value>Python</value>
            <value>C++</value>
        </array>
    </property>
    <!-- List类型注入 -->
    <property name="courseList">
        <list>
            <value>Math</value>
            <value>Chinese</value>
            <value>Computer</value>
        </list>
    </property>
    <!-- Map类型注入 -->
    <property name="courseMap">
        <map>
            <entry key="java" value="90"></entry>
            <entry key="python" value="88"></entry>
        </map>
    </property>
</bean>

List集合中可以存放普通类型,也可以存放引用数据类型,及存放其他类的对象。

<bean id="stu" class="com.wsd.spring.Student">
    <property name="courseList">
        <list>
            <!-- 集合中存放对象 -->
            <ref bean="java"></ref>
            <ref bean="python"></ref>
        </list>
    </property>
</bean>

<bean id="java" class="com.wsd.spring.Course">
    <property name="name" value="java课程设计"></property>
</bean>
<bean id="python" class="com.wsd.spring.Course">
    <property name="name" value="python课程设计"></property>
</bean>

将集合注入的内容提取出来 ,创建一个集合,其他对象可以直接使用ref进行引入,而不需要重新建立一个新的list对象

<?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:util="http://www.springframework.org/schema/util"
       
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           
						   http://www.springframework.org/schema/util
                           http://www.springframework.org/schema/util/spring-util.xsd">
    <!-- 需要在beans中加入util名称空间 -->
    <!-- 提取list集合注入 -->
    <util:list id="courses">
        <value>语文</value>
        <value>数学</value>
        <value>英语</value>
    </util:list>
    <bean id="stu" class="com.wsd.spring.Student">
        <property name="courseList" ref="courses"></property>
    </bean>
</beans>

自动装配

根据指定的规则(属性名或者属性类型),spring自动将属性值进行注入

autowirte属性

属性 说明
byName 根据属性名注入,注入属性的属性名要和注入值bean的id一致
byType 根据属性类型注入,和属性类型相同的属性不能有多个,否则会抛出异常

byName自动注入

<!-- School类对象的id和Student类的属性名一致,都为school -->
<bean id="stu" class="com.wsd.spring.Student" autowire="byName"></bean>

<bean id="school" class="com.wsd.spring.School">
    <property name="name" value="HN_university"></property>
</bean>

byType自动注入(当存在多个类型匹配对象时,会抛出异常)[^相同类型的bean不能定义多个]

<bean id="stu" class="com.wsd.spring.Student" autowire="byType"></bean>

<bean id="hn" class="com.wsd.spring.School">
    <property name="name" value="HN_university"></property>
</bean>

Bean

Spring的bean有两种类型

1、普通bean:在配置文件中定义bean的类型就是返回的类型

2、工厂bean:在配置文件定义bean类型可以和返回类型不一样


Bean的作用域

在Spring中,默认是单例模式,即每次getBean获取的都是同一个对象

public void addrTest() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
    Student stu1 = applicationContext.getBean("stu", Student.class);
    Student stu2 = applicationContext.getBean("stu", Student.class);
    System.out.println(stu1);
    System.out.println(stu2);
}

输出结果为两个对象的地址相同、

com.wsd.spring.Student@481a996b
com.wsd.spring.Student@481a996b

Process finished with exit code 0

通过bean属性的scope可以设置bean的作用域

属性值 说明
Singleton 单实例对象,在加载spring配置文件的时候,就会创建单实例对象[^饿汉式]
Prototype 多实例对象,在使用getBean方法的时候才会创建对象[^懒汉式]
Request 每次创建对象,会将对象放入request中
Session 每次创建对象,会将对象放入session中

Bean的生命周期

对象的创建和销毁过程

  1. 通过构造器创建bean的实例(构造函数)
  2. bean的属性设置值和对其他bean引用(依赖注入)
  3. bean的实例传递给bean后置处理器的方法
  4. 调用bean的初始化的方法[^bean的init-method属性]
  5. bean的实例传递给bean后置处理器的方法
  6. bean对象可以获取
  7. 当容器关闭的时候,会调用bean的销毁方法[^bean的destory-method属性]

生命周期简单演示

Student类

public class Student {
    String name;
    int age;

    public Student() {
        System.out.println("第一步:调用无参构造方法");
    }

    public void setName(String name) {
        System.out.println("第二部:调用set方法注入属性值");
        this.name = name;
    }

    public void initMethod() {
        System.out.println("第三步:执行初始化方法");
    }

    public void destroyMethod() {
        System.out.println("第五步:销毁对象");
    }
}

BeanPost类,处理类

public class BeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第三步:初始化之前执行的方法");
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第五步:初始化之前执行的方法");
        return bean;
    }
}

配置文件

<bean id="stu" class="com.wsd.spring.Student" init-method="initMethod" destroy-method="destroyMethod">
    <property name="name" value="Wsd"></property>
    <property name="age" value="21"></property>
</bean>
<!-- 添加后置处理器,会为配置文件中所有的bean都添加后置处理器 -->
<bean name="beanPost" class="com.wsd.spring.BeanPost"></bean>

测试类

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
Student stu1 = applicationContext.getBean("stu", Student.class);
System.out.println("第六步:得到对象");
// ClassPathXmlApplicationContext中才有close销毁对象方法
applicationContext.close();

引入外部属性文件

当一个类要设置多个属性的时候,要写很多的property标签,不是很方便,修改的时候也比较麻烦。所以,一般将这些比较固定的值或一些相关的值放入到其他的文件中(或者配置文件中)。

常用的比如配置数据库,有很多链接数据库的值(驱动、账户、密码等等),可以将这些属性信息放入到一个配置文件中,再在xml文件中进行读取。

一个数据库连接的例子(配置数据库连接池)

<!-- druid连接池maven依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.9</version>
</dependency>
</dependencies>

如果不使用外部引入文件的方式,只能在xml文件的内部写连接信息,修改起来很麻烦

<!-- 直接配置连接池(值固定) -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="username" value="root"></property>
    <property name="password" value="133813"></property>
</bean>

使用外部引入文件方式配置属性

1、创建一个保存数据库连接信息的配置文件

prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/test
prop.userName=root
prop.password=133813

2、从配置文件中引入配置信息:

(1)引入context命名空间

(2)使用context:property-placeholder引入外部配置文件

(3)使用${}替换value中的内容

<?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:property-placeholder location="dbinfo.properties"></context:property-placeholder>
    <!-- 引入外部属性文件 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!-- 使用${}来引入配置文件中的属性 -->
        <property name="driverClassName" value="${prop.driverClass}"></property>
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.userName}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>
</beans>

Spring管理方法-注解方法

在软件开发中要想使用注解方式,要引入aop的依赖

<!-- Aop -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>

引入Aop之后,要在spring配置文件中添加组件扫描器,用来指定要扫描的package

<!-- base-package可以扫描多个包,包之间用逗号隔开。也可以直接写包的父包,扫描时会同时扫描包中的类和子包 -->
<context:component-scan base-package="com.wsd.spring"></context:component-scan>

在加载spring配置文件时,spring会根据组件扫描器指定的包,创建类的对象

组件扫描器配置

默认情况下,组件扫描器会将base-package中的全部类和子包中的类全部扫描

组件扫描器使用的过滤器默认扫描所有的类

可以指定具体的扫描方式

只扫描指定的包

<context:component-scan base-package="com.wsd.spring" use-default-filters="false">
    <context:include-filter type="annotation" expression="${扫描的包路径}"/>
</context:component-scan>

不扫描指定的包

<context:component-scan base-package="com.wsd.spring" use-default-filters="false">
    <context:exclude-filter type="annotation" expression="${不扫描的包路径}"/>
</context:component-scan>

注解分类

创建对象

Spring针对Bean管理中创建对象提供注解

注解 作用
@Component 普通的注解,可以为所有类创建对象
@Service 一般用在业务逻辑层,或者service层
@Controller 一般用在Controller层
@Repository 一般用在持久层,dao层

注解的使用没有硬性要求,上面的注解都可以创建对象,使用不同的注解可以让开发人员更好的了解到对象在软件中所扮演的角色

使用演示

/***
 * value是类的id,默认值为类名的驼峰命名
 */
@Service(value = "userService")
public class UserService {
    public void add() {
        System.out.println("service add...");
    }
}

属性注入

注解 作用
@AutoWired 根据属性类型自动注入[^byType]
@Qualifier 根据属性名称自动注入[^byName]
@Resource 可以根据类型注入,也可以根据名称注入
@Value 注入普通类型属性

使用演示

1、@AutoWired

@Service(value = "userService")
public class UserService {

    @AutoWired
    UserDao userDaoImpl;

    public void add() {
        System.out.println("service add...");
        userDaoImpl.add();
    }
}

2、@Qualifier

@Qualifier注解要和@AutoWired一起使用

@Service(value = "userService")
public class UserService {

    @Autowired
    @Qualifier(value = "userDaoImpl")
    UserDao dao;

    public void add(){...}
}

3、@Resource

注意:@Resource不是spring提供的,而生javax中的一个注解,使用时需要从外部引入javax.annotation的包

<!-- Javax Annotation -->
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>
@Service(value = "userService")
public class UserService {

    /**按类型注入*/
    @Resource
    UserDao dao;

    /**按名称注入*/
    @Resource(name = "userDaoImpl")
    UserDao dao;
    
    public void add(){dao.add();}
}

4、@Value

@Component(value = "stu")
public class Student {

    @Value(value = "Wsd")
    String name;

    @Value(value = "21")
    int age;
}

全注解开发

不使用配置文件,只使用注解进行程序开发

1、创建配置类,代替xml配置文件

/** 把当前类作为配置类,代替xml配置文件 */
@Configuration
/** 配置组件扫描器,属性是一个数组 */
@ComponentScan(basePackages = {"com.wsd.spring"})
public class SpringConfig { }

2、测试正确性

@org.junit.Test
public void Test() {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    Student stu = applicationContext.getBean("stu", Student.class);
    System.out.println(stu);
}

AOP

面向切面编程

增加功能不修改源代码

AOP的底层技术

  • 动态代理

在给功能模块(类)做增加功能时,分为两种情况:

  1. 类有接口:使用JDK动态代理
  2. 类无接口:使用CGLIB动态代理

JDK动态代理

使用Proxy类中的Proxy.newProxyInstance()方法创建代理对象,实现对类的功能增强

Proxy.newProxyInstance()方法有三个参数:

  1. ClassLoader loader:类加载器
  2. class<?>[] interfaces:增强方法所在的类,这个类可以有多个接口
  3. InvocationHandler h:增强功能

例子: 使用jdk动态代理增强

接口以及实现类

public interface UserDao {
    public int add(int a, int b);
    public int update(int id);
}
public class UserDaoImpl implements UserDao{
    @Override
    public int add(int a, int b) {
        return a + b;
    }
    @Override
    public int update(int id) {
        return id;
    }
}

代理类,增强原方法

class UserDaoProxy implements InvocationHandler{

    private Object object;

    /**
     * 通过构造方法,将被代理类对象传入到代理类中
     * @param object
     */
    public UserDaoProxy(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 前置增强
        System.out.println("原方法前增强 ---->");
        // 原方法
        Object res = method.invoke(this.object, args);
        // 后置增强
        System.out.println("原方法后增强 ----<");

        return res;
    }
}

测试

@Test
public void proxyTest() {
    Class[] interfaces = {UserDao.class};
    UserDao dao = (UserDao)Proxy.newProxyInstance(
        JdkProxy.class.getClassLoader(), 
        interfaces, 
        new UserDaoProxy(new UserDaoImpl()));
    System.out.println(dao.add(4, 5));
}

Aop相关术语

名称 作用
连接点 可以被增强的方法,即上文中的add()update()方法
切入点 实际真正被增强的方法,每个方法都可以被增强,但只增强默写方法
通知(增强) 真正被增强的逻辑部分
切面 动作 ,把通知应用到切入点的过程

通知有多种类型

  1. 前置通知
  2. 后置通知
  3. 环绕通知
  4. 异常通知
  5. 最终通知

AOP

Spring框架一般都是基于AspectJ实现AOP操作,AspectJ不是Spring的组成部分,是一个独立的框架,一般把Spring和AspectJ一起使用

基于AspectJ实现Aop有两种方式

  • xml方式
  • 注解方式

切入点表达式

表明对哪些类进行增强

表达式语法

execution(【权限修饰符】【返回类型】【类路径】【方法名称】(【参数列表】))

1、对com.wsd.spring.dao.UserDao类中的add()进行增强

​ execution(* com.wsd.spring.dao.UserDao.add(..))

​ *:表示任意的返回值

​ (..):表示方法中的参数

2、对com.wsd.spring.dao.UserDao类中的所有方法进行增强

​ execution(* com.wsd.spring.dao.UserDao.*(..))

3、对com.wsd.spring.dao包中的所有类的所有方法进行增强

​ execution(* com.spring.dao.*.*(..))


注解方式

首先创建一个需要被增强的类

/** 被增强的类 */
@Component("user")
public class User {
    public void add() {
        System.out.println("User add ...");
    }
}

创建一个增强类,用来对User类中的方法进行增强

/** 增强类 */
@Component("userProxy")
@Aspect
public class UserProxy {
    // 增强通知
}

配置spring.xml配置文件

  1. 开启context和aop的名字空间
  2. 开启注解扫描
  3. 开启AspectJ生成代理对象
<?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"
       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">
    <!-- 使用时,需要加入context和aop的名称空间 -->
    
    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com.wsd.spring"></context:component-scan>
    <!-- 开启AspectJ生成代理对象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

1、前置通知(注意:通知写在UserProxy增强类中)[^add()执行之前执行]

/** 对User类中的全部方法进行增强 */
@Before("execution(* com.wsd.spring.aop.User.*(..))")
public void beforeEnhance() {
    System.out.println("AspectJ前置通知测试...");
}

2、后置通知[^add()执行之后执行]

/** 后置通知:最终通知,总会执行 */
@After("execution(* com.wsd.spring.aop.User.*(..))")
public void after() {
    System.out.println("AspectJ后置通知测试...");
}

/** 后置通知:得到返回值时执行 */
@AfterReturning("execution(* com.wsd.spring.aop.User.*(..))")
public void afterReturning() {
    System.out.println("得到返回值");
}

/** 后置通知:抛出异常时执行 */
@AfterThrowing("execution(* com.wsd.spring.aop.User.*(..))")
public void afterThrowing() {
    System.out.println("方法执行出现异常!!!");
}

3、环绕通知

/** 后置通知 */
@Around("execution(* com.wsd.spring.aop.User.*(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("环绕之前增强");

    /** 执行被增强的方法 */
    proceedingJoinPoint.proceed();

    System.out.println("环绕之后增强");
}

切入点抽取

上例中每个通知后面的切入点表达式都相同,可以将这些相同的切入点表达式提取出来,避免重复书写

/** 将重复的表达式提取出来 */
@Pointcut("execution(* com.wsd.spring.aop.User.*(..))")
public void getPoint() {}

@After("getPoint()")
public void after() {
    System.out.println("AspectJ后置通知测试...");
}

通知优先级

有多个增强类对同一个方法进行增强,可以设置增强的优先级

可以在增强类上面添加一个注解@Order(value),value是一个数字,数字的值越小,优先级越高

还是上面的User类,不过,这次有两个增强类对add()进行增强

@Component("userProxy")
@Aspect
@Order(2)
public class UserProxy {
    @Before("execution(* com.wsd.spring.aop.User.*(..))")
    public void before() {
        System.out.println("UserProxy前置通知测试...");
    }
}
@Component("userEnhance")
@Aspect
@Order(1)
public class UserEnhance {
    @Before("execution(* com.wsd.spring.aop.User.*(..))")
    public void before() {
        System.out.println("UserEnhance前置通知测试...");
    }
}

结果会先输出UserEnhance前置通知测试...,后输出UserProxy前置通知测试...


配置文件方式

还是对上面的User类中的add()进行增强

首先创建一个增强类,在增强类中写上增强方法

/** 增强类 */
public class UserProxy {
    public void before() {
        System.out.println("UserProxy前置通知测试...");
    }
}

配置文件

  1. 加入aop名字空间
  2. 使用bean方式创建两个对象(被增强类的对象和增强类的对象)
  3. 配置aop
<!-- 创建对象 -->
<bean id="user" class="com.wsd.spring.aop.User"></bean>
<bean id="userProxy" class="com.wsd.spring.aop.UserProxy"></bean>

<!-- 配置aop增强 -->
<aop:config>
    <!-- 切入点 -->
    <!-- id:切入点名字,配置通知时会用到,expression:切入点表达式 -->
    <aop:pointcut id="addBefore" expression="execution(* com.wsd.spring.aop.User.add(..))"/>

    <!-- 配置切面 -->
    <!-- method为增强方法,ref为增强类 -->
    <aop:aspect ref="userProxy">
        <aop:before method="before" pointcut-ref="addBefore"></aop:before>
    </aop:aspect>
</aop:config>

全注解开发

/** 把当前类作为配置类,代替xml配置文件 */
@Configuration
/** 配置组件扫描器 <aop:aspectj-autoproxy /> */
@ComponentScan(basePackages = {"com.wsd.spring"})
/** 开启AspectJ生成代理对象 */
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig { }

JdbcTemplate

spring对jdbc进行的封装,使用JdbcTemplate可以很方便的对数据库进行操作

使用JdbcTemplace需要加入两个依赖

  1. druid
  2. mysql-connector-java
  3. spring-jdbc
  4. spring-tx
  5. spring-orm

CRUD

一个小例子

使用JdbcTemplate对数据库增加信息

新建测试表

+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| user_id  | int(11)     | NO   | PRI | NULL    |       |
| username | varchar(20) | NO   |     | NULL    |       |
| ustatus  | varchar(20) | NO   |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+

创建表对应的实体类

public class User {

    private int userId;
    private String userName;
    private String uStatus;
	
	// 省略set和get方法

配置数据库连接池、创建JdbcTemplate对象、开启组件扫描器

<context:component-scan base-package="com.wsd.spring"></context:component-scan>

<!-- 配置数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="username" value="root"></property>
    <property name="password" value="133813"></property>
</bean>

<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

Dao接口以及实现类

public interface UserDao {
    int addUser(User user);
}
@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao{

    // 注入JdbcTemplate
    @Autowired
    @Qualifier("jdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    @Override
    public int addUser(User user) {
        /***
         * 使用jdbcTemplate的update方法对数据库进行操作
         * sql:执行的sql语句
         * args:可变长参数
         */
        String sql = "insert into t_user values(?,?,?)";
        int count = jdbcTemplate.update(sql,
                user.getUserId(),
                user.getUserName(),
                user.getuStatus()
        );
        return count;
    }
}

Service,用于调用dao接口中的方法

@Service("userService")
public class UserService {

    @Autowired
    @Qualifier(value = "userDaoImpl")
    UserDao dao;

    public void addUser(User user) {
        int status = dao.addUser(user);
        System.out.println(status);
    }
}

测试类

public void addUser() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
    UserService userService = applicationContext.getBean("userService", UserService.class);
    User user = new User(21, "Wsd", "study");
    userService.addUser(user);
}

修改和删除与增加类似,只需要修改sql语句和参数就行了

@Override
public int updateUser(int userId, User user) {
    String sql = "update t_user set user_id=?, username=?, ustatus=? where user_id=?";
    int status = jdbcTemplate.update(sql,
            user.getUserId(),
            user.getUserName(),
            user.getuStatus(),
            userId);
    return status;
}
@Override
public int deleteUser(int userId) {
    String sql = "delete from t_user where user_id = ?";
    int status = jdbcTemplate.update(sql, userId);
    return status;
}

查询

  1. 返回某个值
  2. 返回对象
  3. 返回集合

1、返回某个值,使用jdbcTemplate的queryForObject方法

​ 方法有两个参数

​ 要执行的sql语句

​ 返回值类型的class

@Override
public int getCountUsers() {
    String sql = "select count(*) from t_user";
    /** 参数1,sql语句;sql2,返回类型的class */
    int count = jdbcTemplate.queryForObject(sql, Integer.class);
    return count;
}

2、返回对象,使用jdbcTemplate的queryForObject方法

​ 方法需要三个参数

​ 要执行的sql

​ RowMapper

​ 参数列表

@Override
public User getUser(int userId) {
    String sql = "select * from t_user where user_id = ?";
    /** 有三个参数【sql语句,RowMapper,参数列表】 */
    User user = jdbcTemplate.queryForObject(
            sql,
            new BeanPropertyRowMapper<User>(User.class),
            userId);
    return user;
}

注意BeanPropertyRowMapper构造对象需要提供无参构造方法

3、返回集合,使用jdbcTemplate的query方法

​ 方法需要两个参数

​ 要执行的sql

​ RowMapper

@Override
public List<User> getUsers() {
    String sql = "select * from t_user";
    List<User> userList = jdbcTemplate.query(sql,
            new BeanPropertyRowMapper<User>(User.class));
    return userList;
}

批量操作

batchUpdate(String sql, List<Object[]> batchArgs)

方法会提取List集合中的每一个数组,将数组中的值替换到sql语句中的?,然后通过update执行sql语句

批量添加

@Override
public void batchAdd(List<Object[]> batchArgs) {
    String sql = "insert into t_user values(?, ?, ?)";
    int[] res = jdbcTemplate.batchUpdate(sql, batchArgs);
    System.out.println(Arrays.toString(res));
}

测试

/** batchUpdate会将数组中的值依次赋值到sql语句中的? */
ArrayList<Object[]> arrayList = new ArrayList<>();
Object[] user1 = {14, "Origami", "C"};
Object[] user2 = {15, "Krmi", "C"};

arrayList.add(user1);
arrayList.add(user2);

userService.batchAdd(arrayList);

批量修改

@Override
public void batchUpdate(List<Object[]> batchArgs) {
    String sql = "update t_user set username=?, ustatus=? where user_id=?";
    int[] res = jdbcTemplate.batchUpdate(sql, batchArgs);
    System.out.println(Arrays.toString(res));

}

批量删除

@Override
public void batchDelete(List<Object[]> batchArgs) {
    String sql = "delete from t_user where user_id = ?";
    int res[] = jdbcTemplate.batchUpdate(sql, batchArgs);
    System.out.println(Arrays.toString(res));
}

事务[^待补充]

事物是数据库操作的基本单元, 逻辑上的一组操作,要么都成功要么都失败

经典案例:账户转账

事物的特性[^ACID]:

特性 说明
原子性
一致性
隔离性
持久性

事物通常加入到service层中

事物管理的两种方式:

  1. 编程式
  2. 声明式

声明式事物管理可以通过xml配置文件方式和注解方式实现,在Spring进行事物管理,底层使用AOP原理

Spring事物管理API

PlatformTransactionManager接口


基本使用

1、创建dataSourceTransactionManager对象[^一般用于jdbcTemplate和mybatis]

2、加入tx名称空间并开启事务注解

3、添加事务注解[^@Transactional]

​ 加到类上面,这个类的所有方法都可以添加事务

​ 加到方法上,为这个方法添加事务

@Service("userService")
@Transactional
public class UserService { ... }

事务管理参数

参数 说明
propagation() 事务的传播行为
isolation() 事务的隔离级别
timeout() 超时时间
readOnly() 是否只读
rollbackFor() 回滚
noRollbckFor() 不回滚

xml方式

1、配置事务管理器

2、配置通知

3、配置切入点和切面

<!-- 配置事务管理器 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置通知 -->
<tx:advice id="txAdvice">
    <!-- 指定哪种规则方法上添加事务 -->
    <tx:attributes>
        <!-- 表示以transfer开头的方法 -->
        <tx:method name="transfer*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!-- 配置切入点及切面 -->
<aop:config>
    <aop:pointcut id="transferMethod" expression="execution(* com.wsd.spring.service.UserService.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="transferMethod"></aop:advisor>
</aop:config>

完全注解开发

@Configuration
@ComponentScan("com.wsd.spring")
/** 开启事务 */
@EnableTransactionManagement
public class SpringConfig {

    /** 配置连接池 */
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/test");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("133813");
        return druidDataSource;
    }

    /** 创建JdbcTemplate */
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    /** 创建事务管理器 */
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        return transactionManager;
    }
}
posted @ 2021-01-29 13:30  INEEDSSD  阅读(169)  评论(2编辑  收藏  举报