spring

1. Spring

【参考】狂神说Java

1.1 简介

  • spring:春天 -- 也给软件行业带来了春天!
  • 2002年,首次推出了 Spring 的雏形:interface21框架!
  • 于2004年3月24日诞生
  • Rod Johnson,Spring Framework的创始人
  • 理念: 使现有技术更加容易使用,整合了现有的技术框架

SSH:Struct2 + Spring + Hibernate(全自动)

SSM:SpringMVC + Spring + Mybatis(半自动,更灵活!)


官网:https://spring.io/projects/spring-framework#learn

官方下载地址:https://repo.spring.io/ui/native/release/org/springframework/spring

中文文档:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/

Github:https://github.com/spring-projects/spring-framework


maven 导入依赖

<!-- spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.22</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.22</version>
</dependency>

1.2 优点

  • Spring是一个开源的免费的框架(容器)!
  • Spring是一个轻量级的、非入侵式的框架!
  • 控制反转(IOC),面向切面编程(AOP
  • 支持事务的处理,对框架整合的支持!

小结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程的框架!


1.3 组成


1.4 拓展

  • Spring Boot
    • 一个快速开发的脚手架
    • 基于SpringBoot可以快速开发单个微服务
    • 约定大于配置!
  • Spring Cloud
    • SpringCloud是基于SpringBoot实现的

需求:大多数公司都在使用SpringBoot,而学习SpringBoot的前提是掌握Spring和SpringMVC

弊端:发展太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱”


2. IOC理论

2.1 回顾

  1. UserDao 接口

    public interface UserDao {
        void getUser();
    }
    
  2. UserDaoImpl 实现类

    我们这里创建了3个实现类UserDaoImplUserDaoMysqlImplUserDaoOracleImpl

    public class UserDaoImpl implements UserDao{
        @Override
        public void getUser() {
            System.out.println("默认获取用户数据");
        }
    }
    
    public class UserDaoMysqlImpl implements UserDao{
        @Override
        public void getUser() {
            System.out.println("获取Mysql数据");
        }
    }
    
  3. UserService 业务接口

    public interface UserService {
        void getUser();
    }
    
  4. UserServiceImpl 业务实现类

    public class UserServiceImpl implements UserService {
        private UserDao userDao = new UserDaoImpl();
    
        public void getUser(){
            userDao.getUser();
        };
    }
    

在之前的业务中,如果用户的需求改变,我们需要去修改源代码!比如,如果我们需要新建一个UserDaoOracleImpl实现类,代码如下

public class UserDaoOracleImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("获取Oracle数据");
    }
}

那么我们必须去修改UserServiceImpl的实现代码,当UserDao的实现类越来越多之后,系统耦合度会变得非常高

private UserDao userDao = new UserDaoOracleImpl();

而使用一个Set注入之后,程序不再具有主动性,程序不再由程序员主动创建对象

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

    // 利用set实现动态注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

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

这种IOC思想,从本质上解决了问题,我们不再需要去管理对象的创建,系统耦合性大大降低


2.2 IOC本质

控制反转 IoC(Inversion of Control),是一种设计思想,依赖注入 DI(Dependency Injection)是实现 IoC 的一种方法

个人理解:控制反转 是获取依赖对象的方式反转了,将对象交给容器创建、管理和支配

采用XML方式配置Bean的时候,Bean的定义信息是和实现相分离的;而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方式是依赖注入


2.3 Hello Spring

1)新建一个Hello pojo类,注意:默认是采用无参构造方法(有参构造 也可以通过 下标、类型、参数名 来创建)来创建实例对象,并且这里setter方法是必须的,因为Spring需要它来注入属性

public class Hello {
    private String str;

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }
}

2)新建spring配置文件beans.xml,并配置刚才创建的 java bean 对象。这样我们就可以通过配置文件来动态配置相应的实例对象,而不是像之前那样需要修改源码

注意不管之后是否会使用,这里所配置的实例在 配置文件加载时 都会被初始化创建,而需要使用时我们只需要从容器中取就行

<?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">
    <bean id="hello" class="com.hdbing.pojo.Hello">
        <property name="str" value="Spring" />
    </bean>
</beans>

3)最后来测试一下

public class HelloTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}

再来看一个关于取出的实例测试

public class HelloTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Hello hello = (Hello) context.getBean("hello");
        hello.setStr("hello");
        System.out.println("instance of hello: " + hello);

        Hello hello1 = (Hello) context.getBean("hello");
        hello1.setStr("hello1");
        System.out.println("instance of hello1: " + hello1);

        System.out.println("=============");
        System.out.println(hello.equals(hello1));
        System.out.println("instance of hello: " + hello);
    }
}

控制台输出如下

instance of hello: Hello{str='hello'}
instance of hello1: Hello{str='hello1'}
=============
true
instance of hello: Hello{str='hello1'}

说明了由于spring是在加载配置文件时创建的相应实例对象,所以对于从容器中取出同一个id的实例对象其实是同一个对象!


2.4 Spring配置

别名:name,使用场景:当需要别名来获得实例对象时,可以同时创建多个别名

<bean id="hello" class="com.hdbing.pojo.Hello" name="othername">
	<property name="str" value="Spring" />
</bean>

导入:import,使用场景:当多个配置文件需要合并时,注意相同内容也会被合并


2.5 依赖注入

1)构造器注入

即之前所说的 通过默认无参构造 来创建实例对象 的方式

2)Set方式注入【重点】

  • 创建Student和Address两个pojo类

    • Address

      public class Address {
          private String name;
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          @Override
          public String toString() {
              return "Address{" +
                      "name='" + name + '\'' +
                      '}';
          }
      }
      

    • Student

      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;
      
          @Override
          public String toString() {
              return "Student{" +
                      "name='" + name + '\'' +
                      ", address=" + address +
                      ", books=" + Arrays.toString(books) +
                      ", hobbies=" + hobbies +
                      ", card=" + card +
                      ", games=" + games +
                      ", wife='" + wife + '\'' +
                      ", info=" + 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;
          }
      }
      

  • 修改配置文件

    <?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">
        <bean id="hello" class="com.hdbing.pojo.Hello"/>
        <bean id="address" class="com.hdbing.pojo.Address">
            <property name="name" value="重庆"/>
        </bean>
        <bean id="student" class="com.hdbing.pojo.Student">
            <!--1. 普通值注入,value-->
            <property name="name" value="一洋"/>
            <!--2. Bean注入,ref-->
            <property name="address" ref="address"/>
            <!--3. array-->
            <property name="books">
                <array>
                    <value>红楼梦</value>
                    <value>三国</value>
                    <value>水浒传</value>
                    <value>三国演义</value>
                </array>
            </property>
            <!--4. list-->
            <property name="hobbies">
                <list>
                    <value>听歌</value>
                    <value>码代码</value>
                    <value>看电影</value>
                </list>
            </property>
            <!--5. map-->
            <property name="card">
                <map>
                    <entry key="身份证" value="12312312312"/>
                    <entry key="银行卡" value="32243232353"/>
                </map>
            </property>
            <!--6. set-->
            <property name="games">
                <set>
                    <value>LOL</value>
                    <value>BOB</value>
                </set>
            </property>
            <!--7. null-->
            <property name="wife">
                <null/>
            </property>
            <!--8. properties-->
            <property name="info">
                <props>
                    <prop key="学号">20232132</prop>
                    <prop key="性别">女</prop>
                    <prop key="姓名">小红</prop>
                </props>
            </property>
        </bean>
    </beans>
    

3)拓展方式注入

P命名空间

对应Set注入,即可以直接利用 p:属性名="" 的形式来注入属性

C命名空间

对应构造器注入,即可以直接利用 c:构造器参数="" 的形式来构造对象


4)bean的作用域

通过 scope 属性 进行配置

singleton :单例模式,是Spring的默认方式,表示容器内仅有单个实例对象

prototype:原型模式,表示容器内可以有多个实例对象,即每次从容器中get的时候都会产生一个新的对象

其余几个只有在web开发的场景中才会用上,比如requestsessionapplication


2.6 Bean的自动装配

  • 自动装配是Spring满足bean依赖的一种方式
  • Spring会在上下文中自动寻找,并自动给bean装配属性

在Spring中有三种装配方式

  1. 在 xml 中显式配置
  2. 在 java 中显式配置
  3. 隐式自动装配 bean 【重要】

ByName

会根据对象的setter方法名set后面的值 去找 对应的bean id

ByType

会根据对象属性类型去找对应的 bean,但是必须保证类型全局唯一

注解实现

使用注解前提:

  1. 导入约束:context

  2. 配置注解的支持:<context:annotation-config/>

    <?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>
    
  3. Spring4之后需要保证先导入aop的包


@Autowired

放在属性字段上,先默认通过byType进行寻找,找不到通过byName来实现,此时需要保证属性名与 bean id 一致,而且默认required=true的情况下必须保证相应对象的存在,否则会报空指针异常

在这种情况下不需要setter方法,因为它是通过反射来实现的

required=false表示相应属性可以为null,效果类似于@Nullable,默认为 true

项目中最常用的注解,一般只用 @Autowired 就够用了,如果有需要还可以加上@Qualifier注解

@Qualifier

显式指定 唯一对应的 bean id

@Resource

先通过属性类型找,即默认通过byType实现,找不到就通过名字byName实现,还不行可以通过@Resource(name="")中的 name 来显式指定唯一对应的 bean id

可以看作@Autowired与@Qualifier的组合


2.7 注解开发

指定相应包下的注解生效:<context:component-scan base-package="包名"/>

注解使用只是代替了之前相应的xml配置方式,更简洁!

1)bean

@Component

在 java bean 上使用 @Component 注解,说明这个类被 Spring 管理了

2)属性如何注入

在属性上使用@Value注解

3)衍生注解

@Component有几个衍生注解,使用效果是一致的,都是将某个类注册到Spring容器中,在web开发中,会按照MVC三层架构进行分层

Dao层:@Repository 注解

Service层:@Service 注解

Controller:@Controller 注解

4)自动装配

就是之前讲的@Autowired、@Qualifier和@Resource等注解

5)作用域

@Scope,对应 xml 配置中的scope属性

小结

xml 与注解:

  • xml 更加万能,适用于任何场景,维护简单方便
  • 注解不是自己类使用不了,维护相对复杂

xml 与注解 最佳实践:

  • xml 用于管理 bean

  • 注解只负责完成属性的注入,只需要注意:要让注解生效,就需要开启注解的支持

    <context:component-scan base-package="包名"/>
    <context:annotation-config/>
    

2.8 使用Java方式配置Spring

我们现在完全不需要使用xml的配置方式了,全权交给Java来做

JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能

@Configuration

代表一个配置类,等价于之前的 beans.xml

也会被Spring容器托管,注册到容器中,因为它本来就是一个@Component

@Bean

注册一个bean,等价于 xml 配置中的 bean 标签,所在方法的名字就相当于bean id,所在方法的返回值相当于bean标签中的class属性,即要注入的对象

这种纯Java的配置方式,在SpringBoot中随处可见!


3. AOP

3.1 代理模式

为什么要学习代理模式?因为这是Spring AOP的底层!【面试重点:Spring AOP 和 Spring MVC】

比如租房中的 中介不用再去直接找房东租房

1)静态代理

角色分析

  • 抽象角色:一般会使用接口或抽象类来解决
  • 真实角色:被代理的角色(房东
  • 代理角色:由代理可以完成一些附属操作,并可以对原有操作进行包装中介
  • 客户:访问代理的人(

代码实现

  1. 接口

    public interface Rent {
        public void rent();
    }
    
  2. 真实角色

    //房东
    public class Host implements Rent{
        public void rent(){
            System.out.println("房东要出租房子!");
        }
    }
    
  3. 代理角色

    public class Proxy implements Rent{
        private Host host;
    
        public Proxy() {
        }
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        @Override
        public void rent() {
            seeHouse();
            host.rent();
            hetong();
            fare();
        }
    
        // 看房,房东不能做但是中介可以做的事情
        public void seeHouse(){
            System.out.println("中介带你看房!");
        }
    
        // 收中介费
        public void fare(){
            System.out.println("收中介费");
        }
    
        // 签合同
        public void hetong(){
            System.out.println("签租赁合同");
        }
    }
    
  4. 客户

    public class Client {
        public static void main(String[] args) {
            // 房东要出租房子
            Host host = new Host();
            // host.rent();
    
            // 代理,中介帮房东租房子,但是代理一般会有一些附属操作!
            Proxy proxy = new Proxy(host);
            // 你不用面对房东,直接找中介租房即可!
            proxy.rent();
        }
    }
    

静态代理优缺点

优点

  • 不用改变原有代码!
  • 可以使新角色的操作更加纯粹!不用去关注一些公共业务
  • 公共业务就交给代理角色!实现了业务的分工
  • 公共业务发生扩展时,方便集中管理

缺点

  • 一个真实角色就会产生一个代理角色;代码量会翻倍~开发效率变低(可通过动态代理解决)

2)动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口:JDK动态代理(我们在这里实现)
    • 基于类:cglib
    • java字节码实现:JAVAssist

需要了解两个类:Proxy(得到代理对象),InvocationHandler(通过反射执行代理对象的方法)


代码实现

  1. 接口

    public interface Rent {
        public void rent();
    }
    
  2. 真实角色

    //房东
    public class Host implements Rent{
        public void rent(){
            System.out.println("房东要出租房子!");
        }
    }
    
  3. 代理调用执行器

    // 自动生成代理类
    public class ProxyInvocationHandler implements InvocationHandler {
        //被代理的接口
        private Rent rent;
    
        public ProxyInvocationHandler() {
        }
    
        public void setRent(Rent rent) {
            this.rent = rent;
        }
    
        // 生成得到代理类
        public Object getProxy(){
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);
        }
    
        // 处理代理实例,并返回结果
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            seeHouse();
            //动态代理的本质,就是使用反射机制实现!执行方法
            Object result = method.invoke(rent, args);
            fare();
            return result;
        }
        
        // 看房,房东不能做但是中介可以做的事情
        public void seeHouse(){
            System.out.println("中介带你看房!");
        }
        // 收中介费
        public void fare(){
            System.out.println("收中介费");
        }
    }
    
  4. 调用

    public class Client {
        public static void main(String[] args) {
            // 真实角色
            Host host = new Host();
    
            // 动态代理的调用执行器
            ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
    
            // 设置要代理的对象
            proxyInvocationHandler.setRent(host);
    
            // 得到代理对象
            Rent proxy = (Rent) proxyInvocationHandler.getProxy();
            // 通过代理对象调用方法
            proxy.rent();
        }
    }
    

动态代理优缺点

优点

  • 不用改变原有代码!
  • 可以使新角色的操作更加纯粹!不用去关注一些公共业务
  • 公共业务就交给代理角色!实现了业务的分工
  • 公共业务发生扩展时,方便集中管理
  • 一个动态类代理的是一个接口,一般就是对应一类业务
  • 一个动态代理类可以代理多个类,只要实现同一个接口即可

3.2 AOP

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术

提供声明式事务;允许用户自定义切面

  • 横切关注点:跨越应用程序多个模块的方法或功能,如日志,缓存,事务等
  • 切面(Aspect):横切关注点被 模块化 的特殊对象,是一个类
  • 通知(Advice):切面必须要完成的工作,是类中的一个方法
  • 目标(Target):被通知的对象
  • 代理(Proxy):向目标对象应用通知之后 创建的对象
  • 切入点(PointCut):切面通知 要执行的位置
  • 连接点(JointPoint):与切入点匹配的执行点

【重点】使用AOP织入,需要导入一个依赖包!aspectjweaver

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

方式一:使用Spring API的接口

public class BeforeLog implements MethodBeforeAdvice {
    // method: 要执行的对象方法
    // args: 参数
    // target: 目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行了!");
    }
}
public class AfterLog implements AfterReturningAdvice {

    @Override
    // returnValue: 返回值
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + method.getName() + "方法,返回结果为:" + returnValue);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean-->
    <bean id="userService" class="com.hdbing.service.UserServiceImpl" />
    <bean id="beforeLog" class="com.hdbing.log.BeforeLog" />
    <bean id="afterLog" class="com.hdbing.log.AfterLog" />

    <!--方式一:使用原生Spring API接口-->
    <!--配置aop:需要导入aop的约束-->
    <aop:config>
        <!--切入点: expression: 表达式, execution(要执行的位置!)-->
        <aop:pointcut id="pointcut" expression="execution(* com.hdbing.service.UserServiceImpl.*(..))"/>

        <!--执行环绕增加-->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut" />
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut" />
    </aop:config>
</beans>
public class MyTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 注意动态代理代理的是接口,直接写得到 UserServiceImpl 会报错
//        UserServiceImpl userService = context.getBean("userService", UserServiceImpl.class);
        UserService userService =(UserService) context.getBean("userService");
        userService.add();
    }
}

方式二:自定义实现

public class DiyPointCut {

    public  void  before(){
        System.out.println("方法执行前!");
    }

    public  void  after(){
        System.out.println("方法执行后!");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean-->
    <bean id="userService" class="com.hdbing.service.UserServiceImpl" />

    <!--方式二:自定义类-->
    <bean id="diy" class="com.hdbing.diy.DiyPointCut" />

    <aop:config>
        <!--自定义切面,ref为要引用的类-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="point" expression="execution(* com.hdbing.service.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="point" />
            <aop:after method="after" pointcut-ref="point" />
        </aop:aspect>
    </aop:config>
</beans>

方式三:注解实现

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

    @Before("execution(* com.hdbing.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("方法执行前!");
    }

    @After("execution(* com.hdbing.service.UserServiceImpl.*(..))")
    public  void  after(){
        System.out.println("方法执行后!");
    }

    //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* com.hdbing.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");

        // 获得签名
        Signature signature = jp.getSignature();
        System.out.println(signature);

        // 执行方法
        Object proceed = jp.proceed();

        System.out.println("环绕后");
    }
}
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean-->
    <bean id="userService" class="com.hdbing.service.UserServiceImpl" />

    <!--方式三:注解实现-->
    <bean id="annotationPointCut" class="com.hdbing.diy.AnnotationPointCut" />
    <!--开启注解支持! JDK(默认 proxy-target-class="false") cglib(proxy-target-class="true")-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>

4. 整合Mybatis

步骤

  1. 导入jar包

    • junit
    • mybatis
    • mysql数据库
    • spring相关
    • aop织入
    • mybatis-spring
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.22</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
        <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.22</version>
        </dependency>
    </dependencies>
    
    <!--解决maven静态资源过滤问题,即编译的target中没有相应的 *.xml-->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
    
  2. 编写配置文件

  3. 测试


4.1 回忆Mybatis

  1. 编写实体类

    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    
  2. 编写核心配置文件

    <?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.hdbing.pojo"/>
        </typeAliases>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&amp;serverTimezone=UTC&amp;useSSL=false"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
    
        <!-- 注册UserMapper-->
        <mappers>
            <mapper class="com.hdbing.mapper.UserMapper"/>
        </mappers>
    </configuration>
    
  3. 编写接口

    public interface UserMapper {
        public List<User> selectUsers();
    }
    
  4. 编写 mapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.hdbing.mapper.UserMapper">
        <select id="selectUsers" resultType="user">
            select * from mybatis.user;
        </select>
    </mapper>
    
  5. 测试

    public class MyTest {
        @Test
        public void test() throws IOException {
    
            String resources = "mybatis-config.xml";
            InputStream in = Resources.getResourceAsStream(resources);
            SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
            SqlSession sqlSession = sessionFactory.openSession(true);
    
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> users = mapper.selectUsers();
            for (User user : users) {
                System.out.println(user);
            }
        }
    }
    

注意可能会遇见maven静态资源过滤的问题

<!--解决maven静态资源过滤问题,即编译的target中没有相应的 *.xml-->
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

4.2 Mybatis-Spring

方式一 【重点】

  1. 编写配置文件

    spring-dao.xml

    这个配置文件可固定,将Mybatis相关的数据源和SqlSession交由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:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--DataSource: 使用Spring的数据源替换Mybatis的配置 c3p0 dbcp druid; 我们这里使用Spring提供的JDBC-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url"
                      value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&amp;serverTimezone=UTC&amp;useSSL=false"/>
            <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"/>
            <!--绑定Mybatis配置文件,注意这里classpath:后边千万不能有空格!!!-->
            <property name="configLocation" value="classpath:mybatis-config.xml"/>
            <property name="mapperLocations" value="classpath:com/hdbing/mapper/*.xml"/>
        </bean>
    
        <!--SqlSessionTemplate就是我们使用的SqlSession-->
        <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
            <!--只能通过构造器注入SqlSessionFactory,因为SqlSessionTemplate没有setter方法-->
            <constructor-arg index="0" ref="sqlSessionFactory"/>
        </bean>
        <!--以上这些代码是固定的-->
    </beans>
    

    applicationContext.xml

    这是总的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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <import resource="spring-dao.xml"/>
    
        <bean id="userMapper" class="com.hdbing.mapper.UserMapperImpl">
            <property name="sqlSession" ref="sqlSession"/>
        </bean>
    </beans>
    

    mybatis-config.xml

    这是Mybatis的配置文件

    <?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.hdbing.pojo"/>
        </typeAliases>
    </configuration>
    
  2. 编写mapper实现类,因为需要交给 Spring 管理

    public class UserMapperImpl implements UserMapper{
    
        // 我们在原来所有操作都是用SqlSession来执行,现在都使用SqlSessionTemplate
        private SqlSessionTemplate sqlSession;
    
        public void setSqlSession(SqlSessionTemplate sqlSession) {
            this.sqlSession = sqlSession;
        }
    
        @Override
        public List<User> selectUsers() {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.selectUsers();
        }
    }
    
  3. 测试

    public class SpringMybatisTest {
        @Test
        public void test(){
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    
            for (User user : userMapper.selectUsers()) {
                System.out.println(user);
            }
    
        }
    }
    

方式二

第二种方式取代了之前 在mapper实现类中注入SqlSession 的方式,简化为通过 继承 SqlSessionDaoSupport 来实现,其实底层是通过父类注入SqlSessionFactory实现的,重点掌握第一种方式即可

  1. 编写mapper实现类 继承 SqlSessionDaoSupport

    public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
    
        @Override
        public List<User> selectUsers() {
            return getSqlSession().getMapper(UserMapper.class).selectUsers();
        }
    }
    
  2. 给mapper实现类注入SqlSessionFactory

    <bean id="userMapper2" class="com.hdbing.mapper.UserMapperImpl2">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
    

5. 声明式事务

5.1 回顾事务

  • 要么都成功,要么都失败
  • 事务在项目开发中,十分地重要,涉及到数据一致性问题,不能马虎!
  • 确保完整性和一致性

事务ACID原则

  • 原子性
  • 一致性
  • 隔离性
    • 多个业务操作同一个资源时,防止数据损坏
  • 持久性
    • 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化地写到存储器中!

5.2 Spring中的事务

  • 声明式事务:AOP,以切片的形式,不需要修改代码
  • 编程式事务:需要在代码中进行事务的管理,即try... catch的形式

故意写错delete语句,来模拟事务使用场景

<delete id="deleteUser" parameterType="int">
    deletes from mybatis.user where id=#{id};
</delete>
@Override
public List<User> selectUsers() {

    User user = new User(4, "xiaoliu", "234");

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    mapper.addUser(user);
    mapper.deleteUser(4);

    return mapper.selectUsers();
}

不开启声明式事务的时候,进行测试发现虽然删除用户出错了,它还是插入成功了


配置声明式事务

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

<!--结合AOP实现事务的织入-->
<!--配置事务通知:-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--给哪些方法配置事务-->
    <!--配置事务的传播特性:propagation-->
    <tx:attributes>
        <tx:method name="add" propagation="REQUIRED"/>
        <tx:method name="delete" propagation="REQUIRED"/>
        <tx:method name="update" propagation="REQUIRED"/>
        <tx:method name="query" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!--配置事务切入-->
<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.hdbing.mapper.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

配置声明式事务之后,不需要修改代码,发现并没有插入数据,说明成功开启了事务


为什么需要事务?

  • 如果不配置事务,可能存在数据提交不一致的情况
  • 如果我们不再Spring中去配置声明式事务,我们就需要在代码中手动配置事务!
  • 事务在项目的开发中十分重要,涉及到数据的一致性和完整性,不容马虎!
posted @ 2022-11-19 17:56  黄一洋  阅读(9)  评论(0)    收藏  举报