Spring基本使用

Spring是什么?

Spring是一个开源框架,它由Rod Johnson创建,于2003年发布。Spring框架的主要目标是简化Java企业级应用的开发,通过提供一组全面的解决方案,如依赖注入、控制反转(IOC)、面向切面编程(AOP)、事务管理等,使得开发者能够更加专注于业务逻辑的实现,而不是底层的细节。

官网:https://spring.io/projects/spring-framework#learn
GitHub地址:https://github.com/spring-projects/spring-framework

maven依赖:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>6.2.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.2.3</version>
</dependency>

Spring组成

Spring由多个模块组成,主要包括以下几部分:

  • Spring Core: 核心容器,提供依赖注入(DI)和面向切面编程(AOP)等功能。
  • Spring AOP: 面向切面编程,提供面向切面编程的支持。
  • Spring ORM: 对象关系映射,提供对持久层框架的支持,如Hibernate、JPA等。
  • Spring DAO: 数据访问对象,提供对数据库访问的支持。
  • Spring Web: 提供对Web应用开发的支持,包括Spring MVC等。
  • Spring Context: 提供对上下文的支持,包括Bean的创建、配置和管理等。
  • Spring Web MVC: Spring MVC框架,用于构建Web应用。

IOC理论推导

正常我们创建项目需要完成如下步骤:
1、创建dao类, 假设我们这里有两种获取用户的实现方式,一种是从mysql中获取,另一种是从oracle中获取。

package com.myLearning.dao;

public interface UserDao {
    public void getUser();
}



package com.myLearning.dao;

public class UserDaoMysqlImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("mysql 获取用户");
    }
}


package com.myLearning.dao;

public class UserDaoOracleImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("oracle 获取用户");
    }
}

2、创建service类, 内部需要创建Dao对象,用于调用dao中的方法。

package com.myLearning.service;

public interface UserService {
    public void getUser();
}



package com.myLearning.service;

import com.myLearning.dao.UserDao;
import com.myLearning.dao.UserDaoMysqlImpl;

public class UserServiceImpl implements UserService {
    UserDao userDao = new UserDaoMysqlImpl();

    @Override
    public void getUser() {
        userDao.getUser();
    }
}

3、用户层调用service层实现需求

import com.myLearning.service.UserService;
import com.myLearning.service.UserServiceImpl;

public class MyTest {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        userService.getUser();
    }
}

以上的代码实现虽然满足了需求,但是如果我们现在需要将mysql改为oracle,那么就需要修改service层,即我们需要修改业务层的代码,这显然是不合理的。
所以我们可以修改UserServiceImpl类,将UserDao对象改为接口类型,然后通过set方法进行注入。

package com.myLearning.service;

import com.myLearning.dao.UserDao;

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    // set方法实现UserDao的注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void getUser() {
        userDao.getUser();
    }
}

然后在我们需求变化的时候,我们只需要修改客户端代码,即MyTest类,将UserDaoMysqlImpl改为UserDaoOracleImpl即可。

import com.myLearning.dao.UserDaoOracleImpl;
import com.myLearning.service.UserService;
import com.myLearning.service.UserServiceImpl;

public class MyTest {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(new UserDaoOracleImpl());
        userService.getUser();
    }
}

这样我们就可以在不修改业务层代码的情况下,实现数据库的更换,这就是IOC控制反转的思想。

IOC本质

控制反转是一种思想,就是将对象的创建和对象之间的调用过程交给Spring进行管理,而不是由程序员手动管理,所谓控制反转,就是把创建对象的权利交给Spring,而不是由程序员手动创建对象,即获得对象的方式反转了。
在Spring中,IOC的实现是通过依赖注入(Dependency Injection)来实现的,依赖注入就是将对象之间的依赖关系通过配置文件或者注解的方式,由Spring容器来管理,从而实现对象之间的解耦。

使用Spring进行对象的创建

我们继续修改刚刚的代码,我们现在要使用Spring来创建对象,首先我们需要在pom.xml中添加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
		https://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 在这个配置文件中配置对象,相当于我们在java程序中new一个对象 -->
<!-- 这里的id相当于我们new对象时的变量名,class相当于我们准备new的对象类-->
    <bean id="mysqlImpl" class="com.myLearning.dao.UserDaoMysqlImpl"/>
    <bean id="oracleImpl" class="com.myLearning.dao.UserDaoOracleImpl"/>

    <bean id="userServiceImpl" class="com.myLearning.service.UserServiceImpl">
<!-- 这里property内部,name表示属性名,ref引用bean中创建的对象,value表示实际的值-->
        <property name="userDao" ref="mysqlImpl"/>
    </bean>

</beans>

然后我们就可以直接在Test中获取Spring容器中为我们创建的对象了,而不是自己new对象了

import com.myLearning.dao.UserDaoOracleImpl;
import com.myLearning.service.UserService;
import com.myLearning.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) context.getBean("userServiceImpl");
        userService.getUser();

    }
}

我们现在只需要修改xml配置文件,就可以切换不同的dao实现类了,而不需要修改程序代码,这就是Spring的IOC思想,即对象交由Spring来创建、管理以及装配。

IOC创建对象的方式

我们先创建一个User类:

package com.myLearning;

public class User {
    private String name;

    public User(){
        System.out.println("User 对象被无参构造方式创建了!");
    }

    public User(String name){
        this.name = name;
        System.out.println("User 对象被有参构造方式创建了!");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

然后再beans.xml中配置:

    <bean id="user" class="com.myLearning.User">
        <property name="name" value="张三"/>
    </bean>

在默认情况下,Spring会调用类的无参构造方法来创建对象,如果需要调用有参构造方法,一共有三种方法:
1、通过index设置参数位置

    <bean id="user" class="com.myLearning.User">
        <constructor-arg index="0" value="张三"/>
    </bean>

2、通过type设置参数类型

    <bean id="user" class="com.myLearning.User">
        <constructor-arg type="java.lang.String" value="张三"/>
    </bean>

3、通过name设置参数名称

    <bean id="user" class="com.myLearning.User">
        <constructor-arg name="name" value="张三"/>
    </bean>

Spring配置

bean标签

1、id属性:用于表示对象,相当于变量名,唯一标识符,不能包含特殊字符
2、class属性:用于指示创建的对象类名,填类的全限定名
3、name属性:用于创建该对象的别名,可以包含特殊字符,可以设置多个,多个之间用逗号分隔

    <bean id="user" class="com.myLearning.User" name="user1,user2"/>

alias标签

用于设置别名,可以设置多个,多个之间用逗号分隔

    <alias name="user" alias="user1"/>
    <alias name="user" alias="user2"/>

import标签

用于导入其他配置文件,可以设置多个,多个之间用逗号分隔

    <import resource="beans.xml"/>
    <import resource="beans1.xml"/>
    <import resource="beans2.xml"/>

依赖注入DI

依赖注入(Dependency Injection,DI)是一种设计模式,用于实现控制反转(Inversion of Control,IoC),目的是减少代码间的耦合。
依赖指的是bean对象的创建依赖于容器
注入指的是bean对象中的所有属性,由容器来注入

测试环境搭建

创建实体类

package com.myLearning.pojo;

public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}



package com.myLearning.pojo;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbies;
    private Map<String,String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String[] getBooks() {
        return books;
    }

    public void setBooks(String[] books) {
        this.books = books;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    public Map<String, String> getCard() {
        return card;
    }

    public void setCard(Map<String, String> card) {
        this.card = card;
    }

    public Set<String> getGames() {
        return games;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public String getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

    public Properties getInfo() {
        return info;
    }

    public void setInfo(Properties info) {
        this.info = info;
    }
}

构造器注入

1、通过index设置参数索引

    <bean id="user" class="com.myLearning.User">
        <constructor-arg index="0" value="张三"/>
    </bean>

2、通过type设置参数类型

    <bean id="user" class="com.myLearning.User">
        <constructor-arg type="java.lang.String" value="张三"/>
    </bean>

3、通过name设置参数名称

    <bean id="user" class="com.myLearning.User">
        <constructor-arg name="name" value="张三"/>
    </bean>

set注入

    <bean id="address" class="com.myLearning.pojo.Address">
        <property name="address" value="鸠拉·特恩佩斯特联邦国"/>
    </bean>

    <bean id="student" class="com.myLearning.pojo.Student">
        <!-- 普通值注入     -->
        <property name="name" value="利姆露"/>
<!--       使用bean内对象的引用注入-->
        <property name="address" ref="address"/>
<!--        数组-->
        <property name="books">
            <array>
                <value>突然穿越了怎么办</value>
                <value>帝王心术</value>
                <value>如何偷偷去酒馆不被发现</value>
            </array>
        </property>
<!--        List-->
        <property name="hobbies">
            <list>
                <value>吃好吃的</value>
                <value>看好看的</value>
                <value>玩好玩的</value>
            </list>
        </property>
<!--        Map-->
        <property name="card">
            <map>
                <entry key="id-card" value="10577964"/>
                <entry key="酒馆会员卡" value="100066"/>
            </map>
        </property>
        <!--Set-->
        <property name="games">
            <set>
                <value>竞技场</value>
                <value>举办宴会</value>
            </set>
        </property>
<!--     设置null值-->
        <property name="wife">
            <null/>
        </property>
<!--        Properties-->
        <property name="info">
            <props>
                <prop key="种族">史莱姆</prop>
            </props>
        </property>
    </bean>
</beans>

p注入

使用p命名空间注入属性值需要先导入p命名空间

       xmlns:p="http://www.springframework.org/schema/p"

    <bean id="address2" class="com.myLearning.pojo.Address"
          p:address="朱拉坦派斯特联邦国"/>

使用p命名空间注入可以简化xml配置,相当于之前配置的property

c注入

使用c命名空间注入属性值需要先导入c命名空间

       xmlns:c="http://www.springframework.org/schema/c"

同时使用c注入需要先在类中定义有参构造器

    <bean id="user" class="com.myLearning.pojo.User"
          c:name="张三" c:age="18"/>

使用c命名空间注入可以简化xml配置,相当于之前配置的constructor-arg

bean作用域

bean的作用域包含以下几种

  • singleton:单例(默认):IOC容器中只有一个bean的实例
  • prototype:原型(多例):IOC容器中每次获取bean的时候都会创建一个新的bean
  • request:在web项目中,设置bean的作用域为request表示该bean在一个请求范围内有效
  • session:在web项目中,设置bean的作用域为session表示该bean在一个会话范围内有效
    <bean id="user" class="com.myLearning.pojo.User" scope="singleton"/>

bean自动装配

假设我们有一个人(类),这个人有一只狗

package com.myLearning.pojo;

public class Dog {
    public void wangwang(){
        System.out.println("汪汪");
    }
}


package com.myLearning.pojo;

public class Person {
    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    private Dog dog;


}

然后我们来编写xml配置文件
一般我们需要手动注入创建对象的属性

    <bean id="dog" class="com.myLearning.pojo.Dog"/>
    
    <bean id="person" class="com.myLearning.pojo.Person">
        <property name="dog" ref="dog"/>
    </bean>

但是我们可以设置autowire属性,让spring自动装配,这个属性可以设置为byName和byType,byName表示根据名字自动装配,byType表示根据类型自动装配
根据名字id自动装配,这里搜索的是person类中的dog属性,所以我们需要将小狗类对象的id设置为dog才能被搜索到

    <bean id="dog" class="com.myLearning.pojo.Dog"/>

    <bean id="person" class="com.myLearning.pojo.Person" autowire="byName"/>

根据类型自动装配

    <bean id="dog" class="com.myLearning.pojo.Dog"/>

    <bean id="person" class="com.myLearning.pojo.Person" autowire="byType"/>

使用注解完成自动装配

使用注解完成自动装配需要先开启注解支持,至少保证配置文件包含如下配置:

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

	<context:annotation-config/>

</beans>

使用@Autowired注解完成自动装配,这个注解可以放在属性上,也可以放在set方法上,也可以放在构造方法上

public class Person {
    @Autowired
    private Dog dog;

    public void shout() {
        dog.shout();
    }
}

@Autowired注解默认是先按照类型装配的,如果没有唯一的对应类型,则会试图查找属性名对应的bean, 但如果仍然找不到对应属性名的对象,则会报错,此时需要使用@Qualifier注解指定bean的id
假设我们的配置文件中有如下bean

    <bean id="dog1" class="com.myLearning.pojo.Dog"/>
    <bean id="dog2" class="com.myLearning.pojo.Dog"/>

那么在Person类中,如果使用@Autowired注解,则无法找到唯一的dog对象,此时需要使用@Qualifier注解,它可以指定bean的id

public class Person {
    @Autowired  
    @Qualifier("dog1")
    private Dog dog;

    public void shout() {
        dog.shout();
    }
}

也通过@Ressource注解完成自动装配,这个注解和@Autowired注解类似,使用时放在属性上

使用前需要添加maven依赖

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

它会先按照名称进行装配,如果找不到对应名称的bean,则会按照类型进行装配,如果仍然找不到,则会报错,可以通过(name=“dog1”)指定bean的id

// public class Person {
//     @Resource
//     private Dog dog;

//     public void shout() {
//         dog.shout();
//     }
// }

public class Person {
    @Resource(name="dog1")
    private Dog dog;

    public void shout() {
        dog.shout();
    }
}

使用注解开发

使用注解需要有注解的支持:

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

	<context:annotation-config/>

</beans>

我们创建一个applicatonContext.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
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 指定扫描的包,这个包下的注解会生效   -->
    <context:component-scan base-package="com.myLearning.pojo"/>
    <context:annotation-config/>

</beans>

@Component注解与@Value注解

@Component注解: 将类标识为Spring容器中的一个Bean,Spring会自动扫描并管理这些Bean。相当于在XML配置文件中定义了一个Bean。

@Value注解: 用于为Bean的属性注入值,可以用于字段、方法参数、构造函数参数等。相当于在XML配置文件中为Bean的属性指定了一个值。

package com.myLearning.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {
    @Value("利姆露")
    private String name;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

根据mvc三层架构分层,@Component注解在dao、service、controller层有几个作用相同的注解:

  • @Controller: 用于标识一个控制器类,通常用于处理HTTP请求。
  • @Service: 用于标识一个服务类,通常用于处理业务逻辑。
  • @Repository: 用于标识一个数据访问对象类,通常用于访问数据库。

这几个注解的作用和@Component一样,只是名字不同

@Autowired注解 与 @Qualifier注解

用于自动装配,参考前文介绍

@Scope注解

用于指定对应Bean的作用域,有singleton、prototype、request、session、globalSession等选项。

@Component
@Scope("singleton")
public class User {
    @Value("利姆露")
    private String name;
}

使用纯Java的方式来配置Spring

我们也可以完全不使用XML配置文件,而是使用纯Java的方式来配置Spring。具体步骤如下:

  1. 创建一个配置类,使用@Configuration注解标识该类为一个配置类,这个注解相当于我们之前xml配置文件中的标签,其中可以包含多个bean.
  2. 在配置类中使用@Bean注解来定义一个Bean,这个注解相当于我们之前xml配置文件中的标签。
// 配置类
package com.myLearning;

import com.myLearning.controller.UserController;
import com.myLearning.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // 这个注解同时也会将这个类注册到Spring容器中,因为它本身也是一个@Component
@ComponentScan("com.myLearning") //这个注解相当于我们之前xml配置文件中的<context:component-scan>标签,它会扫描指定包下的所有类,并将它们注册到Spring容器中
public class AppConfig {
    @Bean
    public User getUser() {
        return new User();
    }

    @Bean
    public UserController getUserController() {
        return new UserController();
    }
}

// User类
package com.myLearning.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {
    @Value("利姆露")
    private String name;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


  1. 在需要使用该Bean的地方,使用@Autowired注解来自动装配该Bean。
package com.myLearning.controller;


import com.myLearning.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    @Autowired
    private User user;

    public void printUser() {
        System.out.println(user.getName());
    }
}

在我们使用的时候,我们需要使用AnnotationConfigApplicationContext来加载配置文件,然后获取到UserController的实例

import com.myLearning.AppConfig;
import com.myLearning.controller.UserController;
import com.myLearning.pojo.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        //这里既可以使用刚刚注册的getUserController方法用于获取实例,也可以直接使用UserController的类名获取实例
        // UserController userController = context.getBean(UserController.class);
         UserController userController = (UserController) context.getBean("getUserController");
       userController.printUser();

    }
}

如果我们想将其他配置类导入到一个配置类中,我们可以使用@Import注解,例如:

@Configuration
@Import({AppConfig1.class, AppConfig2.class})
public class AppConfig {
    @Bean
    public UserController getUserController() {
        return new UserController();
    }
}

代理模式

代理模式是一种设计模式,它允许我们通过代理对象来访问目标对象,而不需要直接访问目标对象。在Spring中,代理模式被广泛使用,例如在AOP(面向切面编程)中,代理对象用于在目标方法执行前后添加额外的逻辑。

代理模式的分类

  • 静态代理
  • 动态代理

静态代理

代理模式通常包含三个角色:

  • 抽象主题(Subject):定义了代理类和真实主题的公共接口,这样代理类就可以通过实现这个接口来代理真实主题。
  • 真实主题(Real Subject):实现了抽象主题定义的接口,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理类(Proxy):代理类包含了真实主题的引用,并在调用真实主题的方法前后可以添加一些额外的操作。

假设我们有一个房东要卖房,它们不想直接和购房者打交道,于是请了一个中介来代理它们。房东是真实主题,中介是代理类,购房者是客户端。

我们先定义一个接口,表示房东和中介的公共行为:

package com.myLearning.sellHouse;

public interface Sell {
    void sellHouse();
}

然后我们定义实现这个接口的房东

package com.myLearning.sellHouse;

public class Host implements Sell{
    public void sellHouse() {
        System.out.println("房屋主人卖出房子!");
    }
}

但是我们通常不会直接找到房东买房,而是通过中介代理,所以我们定义一个代理类,它不仅负责帮助房东卖房子,还帮助我们签订合同

package com.myLearning.sellHouse;

public class Proxy implements Sell{
    private Host host;
    public Proxy(Host host) {
        this.host = host;
    }

    private void makeContract(){
        System.out.println("中介协商签合同");
    }

    public void sellHouse() {
        makeContract();
        host.sellHouse();
    }
}

然后我们在客户端就可以使用代理类来买房子和签合同啦

package com.myLearning.sellHouse;

public class Client {
    public static void main(String[] args) {
        Host host = new Host();

        // 房主交给中介卖房
        Proxy proxy = new Proxy(host);
        // 通过中介签合同、买房
        proxy.sellHouse();
    }
}

代理模式的优点

  1. 代理模式能将代理对象与真实对象分离,在一定程度上降低了系统的耦合度。
  2. 代理模式能将一些与真实对象无关的操作,如统计、访问控制等,集中到代理类中完成,这样既可减少真实对象的复杂性,也易维护和扩展。
  3. 代理模式能将真实对象的使用者与真实对象解耦,在一定程度上降低了使用者的复杂性。

动态代理

动态代理是代理模式的一种实现方式,它可以在运行时动态地创建代理类,而不需要提前编写代理类的代码。动态代理通常使用Java的反射机制来实现。

动态代理的原理

动态代理的原理是通过Java的反射机制,在运行时动态地创建代理类,并使用代理类来调用真实对象的方法。具体来说,动态代理的实现步骤如下:

  1. 创建一个实现了InvocationHandler接口的处理器类,该处理器类负责处理代理对象的方法调用。在处理器类中,需要重写invoke方法,该方法会在代理对象的方法被调用时被调用。
  2. 创建一个实现了InvocationHandler接口的处理器对象,并将真实对象作为参数传递给处理器对象的构造方法。
  3. 使用Proxy类的newProxyInstance方法创建代理对象。该方法需要传入三个参数:ClassLoader类加载器,代理对象实现的接口数组,处理器对象。该方法会返回一个代理对象,该代理对象会使用处理器对象来处理方法调用。
  4. 使用代理对象调用真实对象的方法。

动态代理的示例

下面是一个动态代理的示例,该示例使用动态代理来实现一个卖房子的功能。

package com.myLearning.sellHouse;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy implements InvocationHandler {
    private Object target;

    public DynamicProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("中介开始卖房");
        Object result = method.invoke(target, args);
        System.out.println("中介卖房结束");
        return result;
    }

    public static void main(String[] args) {
        Host host = new Host();

        // 创建代理对象
        DynamicProxy dynamicProxy = new DynamicProxy(host);
        ClassLoader classLoader = host.getClass().getClassLoader(); // 获取目标对象的类加载器
        Class<?>[] interfaces = host.getClass().getInterfaces(); // 获取目标对象实现的接口
        Sell sellHouseProxy = (Sell) Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);

        // 使用代理对象调用真实对象的方法
        sellHouseProxy.sellHouse();
    }
}

动态代理的优点

  1. 动态代理可以在运行时创建代理对象,不需要在编译时生成代理类。
  2. 动态代理可以代理任何实现了接口的类,不需要知道具体的实现细节。
  3. 动态代理可以灵活地添加额外的功能,例如日志记录、权限控制等。

Spring AOP

AOP(面向切面编程)是Spring框架中的一个重要特性,它允许我们在不修改原有代码的情况下,通过定义切面(Aspect)来增强或修改原有的功能。

切面(Aspect)

切面是AOP中的一个重要概念,它定义了在哪些地方(Join Points)执行哪些操作(Advice)。切面通常由一个类和一个或多个方法组成,这些方法定义了在特定条件下要执行的操作。

连接点(Join Points)

连接点是程序执行过程中的一个特定点,例如方法调用、异常抛出等。切面可以在这些连接点处执行特定的操作。

通知(Advice)

通知是切面中的一个方法,它定义了在连接点处要执行的操作。通知可以是前置通知(Before Advice)、后置通知(After Advice)、环绕通知(Around Advice)等。

切入点(Pointcut)

切入点是一个表达式,用于定义在哪些连接点处执行通知。切入点可以基于方法名、参数、注解等条件进行匹配。

Spring AOP的使用

万事之前,使用aop我们也需要导入aop相关maven依赖:

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>

方式一

首先我们先编写我们的业务类

package com.myLearning.service;

public interface UserService {
    void add();

    void update();

    void delete();

    void select();
}
package com.myLearning.service;

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("执行了添加用户的方法");
    }

    @Override
    public void update() {
        System.out.println("执行了更新用户的方法");
    }

    @Override
    public void delete() {
        System.out.println("执行了删除用户的方法");
    }

    @Override
    public void select() {
        System.out.println("执行了查找用户的方法");
    }

    public UserServiceImpl() {
    }
}

然后现在我们希望在执行这些方法的前后添加记录日志的代码,但是我们又不希望改动我们已经编写好的业务代码,这个时候我们就可以使用Spring AOP来实现这个功能。

我们先编写我们的Log代码,需要分别实现MethodBeforeAdvice 接口以及AfterReturningAdvice 接口,分别用于在业务方法的前后添加日志记录。

package com.myLearning.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeLog implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("Before Method: " + method.getName());
    }
}
package com.myLearning.log;

import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("ReturnedValue: " + returnValue + " Method: " + method + " Args: " + args);
    }
}

现在我们已经编写好了准备添加到业务前后的Log类,现在我们需要将这两个类通过Spring AOP添加到我们的业务类中,首先我们需要编写一个配置文件applicationContext.xml,也即是我们之前用于配置bean的配置文件,我们需要通过这个配置文件来配置我们的AOP。
类似于我们之前配置的自动装配需要添加配置约束,我们的aop也需要添加配置约束,在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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
		https://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

现在我们在配置文件中添加我们的Log类,业务类,以及配置aop:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
		https://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 指定扫描的包,这个包下的注解会生效   -->
<!--    <context:component-scan base-package="com.myLearning.pojo"/>-->
    <context:annotation-config/>

    <bean id="userService" class="com.myLearning.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.myLearning.log.BeforeLog"/>
    <bean id="afterLog" class="com.myLearning.log.AfterLog"/>

<!--    配置aop,需要导入aop约束-->
<!--    使用Spring API接口配置-->
    <aop:config>
<!--        切入点 expression用于指定要切入的位置(方法)固定表达式为 execution(返回值 类 方法 参数)-->
        <aop:pointcut id="pointcut" expression="execution(* com.myLearning.service.UserServiceImpl.*(..))"/>
<!--        编写环绕通知,指明要加入的通知类,以及要切入的位置-->
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

简单测试一下,发现我们已经在不改动业务代码的情况下,在方法执行前后加入了日志!

import com.myLearning.AppConfig;
import com.myLearning.controller.UserController;
import com.myLearning.pojo.User;
import com.myLearning.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.add();

    }
}

方式二

以上是通过设置通知来配置aop设置的,我们还可以通过直接编写切面类(切面类用于完成如日志的模块功能的类,通知相当于完成功能的方法,我们前面的设置是直接设置通知advisor的方式完成的)来完成aop的配置:

我们先简单编写一个切面类:

package com.myLearning.log;

public class DiyPointCut {
    public void before(){
        System.out.println("before method");
    }

    public void after(){
        System.out.println("after method");
    }
}

然后将这个切面类配置到spring的配置文件中:

    <bean id="userService" class="com.myLearning.service.UserServiceImpl"/>
    <bean id="diyPointCut" class="com.myLearning.log.DiyPointCut"/>

    <aop:config>
        <!--        切入点 expression用于指定要切入的位置(方法)固定表达式为 execution(返回值 类 方法 参数)-->
        <aop:pointcut id="pointcut" expression="execution(* com.myLearning.service.UserServiceImpl.*(..))"/>
<!--      自定义切面  -->
        <aop:aspect ref="diyPointCut">
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

通过这样的方法,我们可以达到与前一种方法同样的效果

方式三

除了以上两种方法,我们还可以通过注解来直接设置切面

首先我们需要写一个切面类,并且在编写时添加注解

package com.myLearning.log;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

// 标识这是一个切面
@Aspect
public class AnnotationPointCut {

//    标识切入点
    @Before("execution(* com.myLearning.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("Before Method");
    }
//    标识切入点
    @After("execution(* com.myLearning.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("After Method");
    }
//     表示环绕切入点
    @Around("execution(* com.myLearning.service.UserServiceImpl.*(..))")
//    参数是连接点,用于执行业务实际方法,以及可以显示执行的业务方法的一些信息
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前");

//        执行实际业务代码
        Object ret = joinPoint.proceed();

        System.out.println("环绕后");
        return ret;
    }
}

然后我们需要再Spring配置文件中配置这个类,并且开启注解支持

    <bean id="userService" class="com.myLearning.service.UserServiceImpl"/>
    <bean id="annotationPointCut" class="com.myLearning.log.AnnotationPointCut"/>
<!--   开启注解支持-->
    <aop:aspectj-autoproxy/>

整合Spring和Mybatis

万事之前,先导包

        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.4</version>
        </dependency>

配置基础Mybatis环境

创建实体类,相对于的数据库可能需要自己参考实体类创建一下,或者可以参考我前一篇博客的数据库创建

package com.myLearning.pojo;

import org.apache.ibatis.type.Alias;

import java.io.Serializable;

@Alias("user")
public class User implements Serializable {
    private int id;
    private String name;
    private String pwd;

    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

创建Mapper接口

package com.myLearning.mapper;

import com.myLearning.pojo.User;

import java.util.List;

public interface UserMapper {
    List<User> selectAllUser();
}

创建Mapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--用于绑定刚刚创建的Dao(Mapper)接口-->
<mapper namespace="com.myLearning.mapper.UserMapper">
    <select id="selectAllUser" resultType="com.myLearning.pojo.User">
        select * from user
    </select>
</mapper>

创建mybatis-config.xml配置文件, 注意,我这里并没有配置数据源以及mapper映射,这是因为我们一会后会将Spring与mybatis整合,我们会在Spring的配置文件中配置数据源之类的东西

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <typeAliases>
        <package name="com.myLearning.pojo"/>
    </typeAliases>

    <!--    <settings>-->
<!--        <setting name="" value=""/>-->
<!--    </settings>-->

</configuration>

设置Spring的配置

在我们创建好了mybatis基础环境后,我们现在可以来编写Spring的配置文件了,在这个文件中,我们主要需要配置
数据源类org.springframework.jdbc.datasource.DriverManagerDataSource,这个就是我们之前在Mybatis中配置的数据源,我们现在在Spring中配置。
工厂类org.mybatis.spring.SqlSessionFactoryBean, 这个是我们之前在Mybatis工具类中创建的工厂类,现在我们将其配置到Spring中。
SqlSession类 org.mybatis.spring.SqlSessionTemplate,这个就是我们之前在Mybatis工具类中创建的SqlSession类,主要用于执行我们的sql语句的,现在我们将其配置到Spring中。
这几个类

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

    <!-- 指定扫描的包,这个包下的注解会生效   -->
    <!--    <context:component-scan base-package="com.myLearning.pojo"/>-->
    <context:annotation-config/>

<!--首先我们需要配置一个数据源,我们现在使用Spring的数据源代替mybatis的-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

<!--我们还需要一个sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/myLearning/mapper/*.xml"/>
    </bean>

<!-- 获得sqlSession对象-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

</beans>

现在我们有了sqlSession后,我们就可以创建一个UserMapper的实现类UserMapperImpl,用来通过sqlSession对象来执行sql语句了。

package com.myLearning.mapper;

import com.myLearning.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

public class UserMapperImpl implements UserMapper {
    private SqlSessionTemplate sqlSession;
    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public List<User> selectAllUser() {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        return userMapper.selectAllUser();
    }
}

同样,我们把它注册到Spring中

<!--    获得UserMapper对象-->
    <bean id="userMapper" class="com.myLearning.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>

当然,我们可能需要一个applicationContext.xml来管理所有的Spring配置,在其中导入我们刚刚创建的Spring-dao.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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
		https://www.springframework.org/schema/aop/spring-aop.xsd">

<import resource="spring-dao.xml"/>

</beans>

简单测试一下

package com.myLearning.dao;

import com.myLearning.mapper.UserMapper;
import com.myLearning.pojo.User;
import com.myLearning.util.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class UserMapperTest {
    @Test
    public void selectUserListTest(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = (UserMapper)context.getBean("userMapper");

        List<User> users = userMapper.selectAllUser();
        for (User user : users) {
            System.out.println(user);
        }
    }

}

当然,我们还可以有另一种方式来编写UserMapperImpl类,即让它继承SqlSessionDaoSupport类,这样我们就可以直接使用getSqlSession()方法来获取SqlSession对象,而不需要自己创建SqlSession对象。

package com.myLearning.mapper;

import com.myLearning.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> selectAllUser() {
        UserMapper userMapper = getSqlSession().getMapper(UserMapper.class);
        return userMapper.selectAllUser();
    }
}

但要注意,此时我们注册的时候,需要注入的不再是SqlSessionTemplate,而是SqlSessionFactory。

<!--    获得UserMapper对象-->
    <bean id="userMapper" class="com.myLearning.mapper.UserMapperImpl">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>

使用Spring配置声明式事务

首先要确保Spring的配置文件有事务的声明空间,即引入tx命名空间,类似如下内容,主要是包含tx相关的:

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

</beans>

首先我们需要再Spring配置文件中配置事务管理器

<!--    配置声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="dataSource" />
    </bean>

然后我们使用aop的通知来实现事务,这里Spring已经为我们写好了事务类型的通知,我们只需要简单配置即可

<!--    配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--        给哪些方法配置事务-->
        <tx:attributes>
            <tx:method name="add" propagation="REQUIRED"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

然后我们需要使用aop配置将通知和切入点结合起来

<!--    aop配置事务切入-->
    <aop:config>
<!--        设置切入点,给com.myLearning.mapper包内所有类中的所有方法配置事务-->
        <aop:pointcut id="txPointCut" expression="execution(* com.myLearning.mapper.*.*(..))"/>
<!--    设置通知-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

以此,我们就为我们的com.myLearning.mapper包内所有类中的所有方法配置了事务管理了

笔记总结于视频:https://www.bilibili.com/video/BV1WE411d7Dv?vd_source=16bf0c507e4a78c3ca31a05dff1bee4e&spm_id_from=333.788.videopod.episodes

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值