Spring详解

Spring解决企业应用开发的复杂性

histroy

创始人:Rod Johnson

spring理念使现有技术更加容易使用,像是一个粘合剂

SSM: Spring+SpringMVC+MyBatis

官网: Spring | Home 中文网: Spring 中文网 (springref.com)

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

GitHub上的Spring项目: https://github.com/spring-projects/spring-framework

官方API:https://spring.io/projects/spring-framework#learn
官方文档阅读**:https://docs.spring.io/spring-framework/docs/current/reference/html/**

核心技术文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core

maven:spring-webmvc,spring-jdbc

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

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

优点

轻量级,非入侵式(不会改变原本代码)

控制反转(IOC),面向切面编程(AOP)

支持事务和框架整合


七大组件

现代化的java开发,就是基于spring的开发

Spring七大组件

Spring七大组件_四舍五入不如六的博客-优快云博客(https://blog.youkuaiyun.com/xuemin__/article/details/103325402)

七大组件

Spring路线


IOC控制权反转

  1. IOC是一种设计思想,DL(依赖注入)是实现IOC的一种方法.
  2. 是一种通过描述与第三方,生产或获取,特定对象的方式.
  3. 在Spring中由IOC容器实现,实现方法是DL

原型

原先的对象,由我们(程序员)来决定是什么样的

UserDao userDao = new UserDaoImpl();

现在的对象,传进来,不再直接由我们来决定了.控制权交给别人

public interface UserDao {}
public class UserDaoImpl implements UserDao{}
public class UserService {
  
    //这是以前的方式,控制权在我们
    private UserDao oldUserDao = new UserDaoImpl();

    UserDao userDao;

  	//现在控制权不在我们手里了,控制层传进来什么就是什么
    public void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }

}

这几个类全部同包

public class Servlet {
  public static void main(String[] args) {
    
    UserDaoImpl userDao = new UserDaoImpl();

    UserService userService = new UserService();
    userService.setUserDao(userDao);

  }


}

Hello Spring

包结构:com.changGe.li.pojo

User实体类

import lombok.Data;

@Data
public class User{

  private String names;
  
  private Student student;
  
}

Student

public class Student { }

Spring配置文件:beans.xml

要放在resource包下

官方模板

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

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

我们自己写的

<!--创建一个pojo.User类型的对象,名字(id)user
  创建后把对象放入spring容器中.-->
<bean id="student" class="com.changGe.li.pojo.Student"/>

<bean id="user" class="com.changGe.li.pojo.User">
  <!--给其中的属性(name)names赋值(value)为"李长歌"-->
  <property name="names" value="李长歌"/>

  <!--给Student类型的属性student赋值,引用上面的student对象的值-->
  <property name="student" ref="student"/>

</bean>

测试使用

import com.changGe.li.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {

  @Test
  public void sprintTest(){
    //根据类路径读取spring配置文件,获取Spring上下文对象,也可以说获取Spring容器
    ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

	//根据系统路径来读取配置文件
	//ApplicationContext context = new FileSystemXmlApplicationContext("F:\\JAVA\\SpringReview\\src\\main\\resources\\bean.xml");


    //spring上下文对象从容器中,得到名叫user的对象,类型是User
    User user = application.getBean("user", User.class);
    //只能按类型查找
    //User user = applicationContext.getBean(User.class);

    String name = user.getNames();//李长歌
    System.out.println(name);
  }

}

ClassPathXmlApplicationContext一直向上找,可以看到途中实现了一个DefaultResourceConifg类,这个类就是用来通过流,读取配置文件的.而ClassPathXmlApplicationContext最终相当于实现了ApplicationContext**

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

IOC创建对象的方式

Spring的IOC:对象的一切交由Spring操作,如创建,管理,赋值等.

默认调用无参构造器创建

上面的配置,给User类加上全参构造器后,就报错:找不到这样的类

@Data
@AllArgsConstructor
public class User{

  private String names;

  private Student student;

}
<!--有参构造器把无参构造器顶替掉,需要配置构造器的参数-->
<bean id="user" class="com.changGe.li.pojo.User">
  <!--分别是以传参索引下标,类型和名字和为传参赋值,三个选一个-->
  <constructor-arg index="0" value="李长歌"/>
  <constructor-arg type="java.lang.String" value="李长歌"/>
  <constructor-arg name="names" value="李长歌"/>
</bean>

spring容器中创建的bean默认是单例的

User user = application.getBean("user",User.class);
User user1 = application.getBean("user",User.class);

System.out.println(user == user1);//true

Spring配置

别名

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

原先的和别名都能使用

User user = application.getBean("user",User.class);
User user1 = application.getBean("user1",User.class);

System.out.println(user == user1);//true

name也可以定义别名,并且可以同时定义多个别名

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

<!--可以与alias并行存在-->
<bean id="user" class="com.changGe.li.pojo.User" name="user1,user2 user3; use4">
  <constructor-arg index="0" value="李长歌"/>
</bean>
User user = application.getBean("use4",User.class);
User user1 = application.getBean("user3",User.class);

System.out.println(user == user1);//true

import(导入)多人开发时,最终将所有的配置文件,导入联合在一起
遇到相同的资源时,spring容器会后者覆盖前者

beans.xml只有一个user的bean

<bean id="user" class="com.changGe.li.pojo.User" name="user1,user2 user3; use4">
  <constructor-arg index="0" value="李长歌"/>
</bean>

aliasTest.xml导入beans.xml后,alias标签就不会报红了

<import resource="beans.xml"/>
<alias name="user"  alias="user1"/>

依赖注入

构造器注入,就是前面的constructor-arg的方式


Set注入(调用set()实现属性值注入,称为“依赖注入”)

依赖注入不是两个单词,是bean的属性注入依赖于spirng容器

  • 所有的bean创建依赖于spring容器
  • bean中的所有属性,由spring来注入
@Data
@AllArgsConstructor
@ToString
public class User{
  
  private Map<String,String> card;
  private Set<String> games;
  private String wife;
  private Properties info;

}
<!--value=“”就是给属性赋空值-->
<bean id="user" class="com.changGe.li.pojo.User">
  
  <!--set注入只是把constructor-arg换成property-->
  <constructor-arg name="card">
    <map>
      <entry key="name" value="李长歌"/>
      <entry key="age" value="18"/>
      <entry key="sex" value=""/>
    </map>
  </constructor-arg>

  <constructor-arg name="games">
    <set>
      <value>LOL</value>
      <value>CF</value>
      <value>CS</value>
    </set>
  </constructor-arg>

  <constructor-arg name="wife">
    <null/>
  </constructor-arg>

  <constructor-arg name="info">
    <props>
      <prop key="driver">com.jdbc.cj.mysql.Driver</prop>
      <prop key="url">jdbc:mysql:///smbms</prop>
      <prop key="username">root</prop>
      <prop key="password">root</prop>
    </props>
  </constructor-arg>

</bean>
User user = application.getBean("user",User.class);

System.out.println(user);
User(card={name=李长歌, age=18, sex=}, games=[LOL, CF, CS], wife=null, info={password=root, url=jdbc:mysql:///smbms, driver=com.jdbc.cj.mysql.Driver, username=root})

命名空间注入

  • c命名空间是找构造器注入,p命名空间是找set()注入
  • uitl命名空间定义特殊类型的bean.

xmlns = xml name space = 开启一个名为p的命名空间

  • xmlns:p=“http://www.springformework.org/schema/p”

  • xmlns:c=“http://www.springformework.org/schema/c”

util的命名空间,可以用来理解为通过set()注入map,list等特殊类型属性的.
xmlns:util=“http://www.springframework.org/schema/util”
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd">

<!--给Test对象中的属性wife赋值静态字段java.lang.Math.PI
他可以引用其他bean-->
   <bean id="test" class="Test">
      <property name="wife">
          <util:constant static-field="java.lang.Math.PI"/>
      </property>
   </bean>
<!--导入约束-->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

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

xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="user" class="com.changGe.li.pojo.User" c:card-ref="card" c:games-ref="games"
          c:wife="李长歌" c:info-ref="info"/>

<util:map id="card" key-type="java.lang.String" value-type="java.lang.String">
  <entry key="1" value="2"/>
</util:map>

<util:set id="games">
  <value>LOL</value>
</util:set>

<util:properties id="info">
  <prop key="driver">Driver</prop>
</util:properties>
User(card={1=2}, games=[LOL], wife=李长歌, info={driver=Driver})

Bean的作用域

<!--默认是单例的-->
<bean scope="singleton"/>

<!--设置成原型的-->
<bean scope="prototype"/>
//测试
User user = application.getBean("user",User.class);
User user1 = application.getBean("user",User.class);

System.out.println(user==user1);

其他的作用域:request,session,application和websocket,实际就是你的bean可以存活多久,如设置成request就是在一次会话中存活。


bean的自动装配autowire

  • byName和byType都是调用set()
@Data
public class User{
  private Student student;
}
public class Student { }

byName:按bean的id来查找

如bean的id为name,就自动匹配setName()

<bean id="student" class="com.changGe.li.pojo.Student"/>
<!--User类中有一个setStudent()-->
<bean id="user" class="com.changGe.li.pojo.User" autowire="byName"/>

byType:按类型查找

<!--被自动装配的bean,可以不写id-->
<bean class="com.changGe.li.pojo.Student"/>
<!--User对象中有一个setStudet(Student student)-->
<bean id="user" class="com.changGe.li.pojo.User" autowire="byType"/>

测试

ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

User user = application.getBean("user",User.class);

System.out.println(user.getStudent());
  • 这些都是隐式的自动装配

  • byName需要保证bean的id唯一性

  • byTyep要保证class的唯一性
    其他还有constructor(走构造器,默认是byName模式),**default(不自动注入)**可以选择。


注解式自动装配@Autowired(byType式)

context命名空间和网址

<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/>
@Data
public class User{

  //自动装配,可以不用写set方法了
  //前提是这个student已经存在ioc容器中了,且符合byName的规则
  @Autowired
  private Student student;

}
<bean id="student" class="com.changGe.li.pojo.Student"/>
<bean id="user" class="com.changGe.li.pojo.User"/>
//注解配置应用上下文:用纯注解的方式获取IOC容器
ApplicationContext application = new AnnotationConfigApplicationContext(com.changGe.li.pojo.User.class);

User user = application.getBean("user",User.class);

System.out.println(user.getStudent());
ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

User user = application.getBean("user",User.class);

System.out.println(user.getStudent());

@Nullable规定字段可以为空
@Autowired
@Nullable
private Student student;

//@Nullable同时存在,没有影响
public void setStudent(@Nullable Student student){
  this.student = student;
}
User user = application.getBean("user",User.class);

user.setStudent(null);

//正常运行
System.out.println(user.getStudent());//null

@Autowired有个required属性,默认为true,不允许为空

public @interface Autowired {
  boolean required() default true;
}

显式定义required,使字段可以为空

//三者可以同时存在
@Autowired(required = false)
@Nullable
private Student student;

public void setStudent(@Nullable Student student){
  this.student = student;
}

@Qualifier规定使用哪个bean进行自动注入

定义了相同类型的两个bean,但是只有一个有id

<bean class="com.changGe.li.pojo.Student"/>
<bean id="student1" class="com.changGe.li.pojo.Student"/>

@Qualifier指定使用哪个bean

@Autowired
@Qualifier("student1")
private Student student;

@Resource:byName式自动装配

@Data
public class Student {

  private String name;

}

两个student对象,分别给name属性赋值

<bean id="student" class="com.changGe.li.pojo.Student">
  <property name="name" value="student"/>
</bean>

<bean id="student1" class="com.changGe.li.pojo.Student">
  <property name="name" value="student1"/>
</bean>
@Data
public class User{

  //两者可以同时存在,没有
  @Resource(name = "student")
  private Student student;

  @Resource(name = "student1")
  public void setStudent(Student student){
    this.student = student;
  }

}

测试结果最后是student1

ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

User user = application.getBean("user",User.class);

System.out.println(user.getStudent().getName());//student1

@Autowrite要求对象必须存在

@Resource默认先通过byName来实现,找不到名字,通过byType实现.

@Resource
private Student student;

@Resource
public void setStudent(Student student){
  this.student = student;
}

byType模式匹配时,只能有一个bean,多了就报错

<bean  class="com.changGe.li.pojo.Student">
  <property name="name" value="student1"/>
</bean>

注解开发

注解必须要有aop包支持,spring-webmvc默认就有了

spring-aop包

首行需要配置包扫描

<!--配置包扫描:让这个包下的注解可以生效-->
<context:component-scan base-package="com.changGe.li.pojo"/>
<context:annotation-config/>
@Data

//相当于<bean id="user" class="com.changGe.li.pojo.User"/>
@Component//确定这个类是一个组件
public class User{

  //相当于<property name="name" value="李长歌"/>
  @Value("李长歌")
  private String name;

  @Value("李世民")
  public void setName(String name){
    this.name = name;
  }

}
ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

User user = application.getBean("user",User.class);

System.out.println(user.getName());//李世民

compoent延伸注解

@Repositroy == dao层

@Data
@Repository
public class User{

  @Value("李长歌")
  private String name;

}
User user = application.getBean("user",User.class);

System.out.println(user.getName());//李长歌

service服务层

@Data
@Service
public class User{

  @Value("李长歌")
  private String name;

}

controller控制层

@Data
@Controller
public class User{

  @Value("李长歌")
  private String name;

}

注解配置单例模式

SpringBoot:@Scope注解学习 - 寒烟濡雨 - 博客园 (cnblogs.com)

@Data
@Controller
@Scope("singleton")
public class User{

  @Value("李长歌")
  private String name;

}
User user = application.getBean("user",User.class);
User user1 = application.getBean("user",User.class);

System.out.println(user == user1);//true

@Scope("prototype")//false

最理想的状态是:xml管理bean,注解赋值


JavaConfig:用注解的方式配置spring

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

public class User{}
public class Test1 {}
@Configuration//这个注解的底层源码,也是被component注解了的
@ComponentScan("com.changGe.li")//相当于扫描包
@Import(Test1.class)//导入其他的javaConfig类
public class Test{

    @Bean//相当于<bean>语句,方法名是id,返回值是个User的对象
    public User getUser(){
        return new User();
    }

}
@Test
public void sprintTest(){

  ApplicationContext application = new AnnotationConfigApplicationContext(com.changGe.li.pojo.Test.class);

  User user = application.getBean("getUser",User.class);

  System.out.println(user);

}

代理模式

能组合,就不继承

面向对象的七大原则: https://blog.youkuaiyun.com/qq_34760445/article/details/82931002

改动别人的代码,在公司,是大忌

能加层,就不要改代码


动态代理

思想本质:以前的静态代理:房东(接口)要出租房子(接口),需要找到中介(代理类).然后中介通过传进来的对象,调用传进来的对象的方法.这个就是静态代理

//接口有一个方法,需要实现
public interface UserService {

  void add();
  void delete();
  void update();
  void query();
}

动态代理本质就是静态代理中的代理类,动态生成了.

还是房东,要出租房子.本来应该需要一个代理类的.

package com.changGe.dynamicProxy;

//实现了接口的方法
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("add执行中");
    }

    @Override
    public void delete() {
        System.out.println("delete执行中");
    }

    @Override
    public void update() {
        System.out.println("update执行中");
    }

    @Override
    public void query() {
        System.out.println("query执行中");
    }

}

但是现在有个中间的工具类,实现了Invacationhanderl接口下的invoke方法.

这个方法中有三个参数:object不用管,method(就是接口中的方法),还有args(参数列表).

动态代理对象调用方法时,就自动调用的这个方法.method.invoke(这个方法要执行)(object(bject(执行哪个对象的),args(要执行的参数));

最后method.invoke返回我们一个object对象,这个不就是方法执行完后的返回值吗?

而Proxy.newProxyIntanfice(this.getClass.getClassloader(房东),target.getintanfices(接口),this(invaicationhander)),最后 的返回值就是我们要的 动态生成的 代理类.

package com.changGe.dynamicProxy;

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

//这是一个代理工具类:用来动态生成代理对象
public class Dynamic implements InvocationHandler {

  //要实现的接口
  private Object target;

  public void setTarget(Object target){
    this.target = target;
  }

  //传入真实角色,要动态代理的接口,怎么代理(就是我们自己的动态代理工具类)
  //返回一个动态代理对象
  public Object getProxy(){
    return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),
    this);
  }


  //ScriptKill被动态代理后,实际是调用这个方法,来动态代理他本来要实现的方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    //对代理方法进行增强
    System.out.println(method.getName()+"方法被执行了");

    //这个方法执行后,会有一个object类型的返回值
    Object invoke = method.invoke(target, args);

    System.out.println(method.getName()+"方法执行完了");


    return invoke;

  }

}

最后动态生成的代理类调用方法,就是和静态代理一样了.

package com.changGe.dynamicProxy;

public class Test {
  public static void main(String[] args) {

    //真实角色
    UserServiceImpl user = new UserServiceImpl();

    //动态代理工具类
    Dynamic dynamic = new Dynamic();
    dynamic.setTarget(user);

    //获取动态代理对象
    UserService proxy = (UserService)dynamic.getProxy();

    //动态代理对象调用方法
    /**
     * add方法被执行了
     * add执行中
     * add方法执行完了
     */
    proxy.add();

  }

}

如果我们改变了其中的target,就是修改了接口,而这个接口一般是一类业务的方法总和.

所以我们修改其中的target,就是修改了一类业务

同时,因为最后其实调用的invoke方法,我们就可以在这个方法执行的前后,增加操作了,这就是方法增强

这是Spring AOP(面向切面编程)的原理


AOP面向切面编程

实际作用就是:不改变原有代码的情况下,对原有代码进行增强

aop的作用

aop流程

通过预编译方式,和运行期动态代理,实现程序功能的 统一维护

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

AOP织入依赖

org.aspectj.aspectjweaver1.9.4
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.8.RC1</version>
</dependency>

实现方式

Spring的原生api接口实现

UserService实现了接口User

public interface User {

  void add();
  void delete();
  void query();

}
package com.changGe.li.pojo;

import com.changGe.li.pojo.User;

public class UserImpl implements User{

  @Override
  public void add() {
    System.out.println("add实现了");
  }

  @Override
  public void delete() {
    System.out.println("delete实现了");
  }

  @Override
  public void query() {
    System.out.println("query实现了");
  }

}

自定义类实现方法增强的接口,如MethodBeforeAdvice(切入点之前),AfterRetruningActive(切入点返回值后)

package com.changGe.li.pojo;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

//建议之前的方法:切入点之前的方法
public class LogBefore implements MethodBeforeAdvice {

  @Override
  public void before(Method method, Object[] args, Object target) throws Throwable {
    System.out.println("切入点之前的方法");
  }

  public void beforeTest(){
    System.out.println("切入点之前的方法的测试方法");
  }

}
package com.changGe.li.pojo;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

//afterReturningAdvice返回值后的建议:切入点返回值之后的方法
public class LogAfter implements AfterReturningAdvice {
  @Override
  public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
    System.out.println("切入点返回值之后的方法");
  }

  public void beforeTest(){
    System.out.println("切入点返回值之后的方法的测试方法");
  }

}

beans.xml中导入aop依赖:可以直接<aop enter+回车

<!--添加约束-->
xmlns:aop="http://www.springframework.org/schema/aop"

<!--约束网址-->
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd

配置包扫描

<!--配置包扫描:让这个包下的注解可以生效-->
<context:component-scan base-package="com.changGe.li.pojo"/>
<context:annotation-config/>

配置bean和aop织入

<bean id="userService" class="com.changGe.li.pojo.UserImpl"/>

<bean id="before" class="com.changGe.li.pojo.LogBefore"/>
<bean id="after" class="com.changGe.li.pojo.LogAfter"/>

<aop:config>
  <!--切入点在哪里执行:User类下所有的方法,传参为所有类型-->
  <aop:pointcut id="pointcut" expression="execution(* com.changGe.li.pojo.User.*(..))"/>

  <!--导入增强方法分别是before对象和after对象,这两个接口的方法被我们实现了.
		spring会自动判断方法是在哪里增强
	-->
  <aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
  <aop:advisor advice-ref="after" pointcut-ref="pointcut"/>

</aop:config>

测试的时候,spring动态代理的接口,所以application.getBean时,传参与返回值必须是接口类型的

ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

User user = application.getBean("userService",User.class);
user.add();

aop切面定义

自定义切面类

package com.changGe.li.pojo;

public class Diy {

  public void before(){
    System.out.println("这是before方法");
  }

  public void after(){
    System.out.println("这是after方法");
  }

}

引用切面类的增强方法

<bean id="userService" class="com.changGe.li.pojo.UserImpl"/>
<bean id="diy" class="com.changGe.li.pojo.Diy"/>

<aop:config>

  <!--引用我们自定义的切面类-->
  <aop:aspect ref="diy">
    <aop:pointcut id="pointcut" expression="execution(* com.changGe.li.pojo.User.*(..))"/>

    <!--导入增强方法-->
    <aop:before method="before" pointcut-ref="pointcut"/>
    <aop:after method="after" pointcut-ref="pointcut"/>
  </aop:aspect>

</aop:config>
User user = application.getBean("userService",User.class);
user.add();
//运行结果
这是before方法
add实现了
这是after方法

注解aop
package com.changGe.li.pojo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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 Diy {

  @Before("execution(* com.changGe.li.pojo.User.*(..))")
  public void before(){
    System.out.println("这是before方法");
  }

  @After("execution(* com.changGe.li.pojo.User.*(..))")
  public void after(){
    System.out.println("这是after方法");
  }

  @After("execution(* com.changGe.li.pojo.User.*(..))")
  public void after1(){
    System.out.println("这是after方法1");
  }

  //环绕增强:代理对象执行方法的时候,把所有的增强方法都执行了一遍
  @Around("execution(* com.changGe.li.pojo.User.*(..))")
  //point代表获取被切入的点
  public void around(ProceedingJoinPoint point){
    System.out.println("环绕前");

    //获取签名,也就是方法名()
    Signature signature = point.getSignature();
    System.out.println("签名是:"+signature);

    Object proceed = null;
    try {
      //执行方法
      proceed = point.proceed();
    } catch (Throwable e) {
      e.printStackTrace();
    }

    System.out.println("环绕后");
    System.out.println("返回值是:"+proceed);
  }

}

开启注解增强,就不用配置其他的aop织入了

<!--开启注解式aop织入支持-->
<aop:aspectj-autoproxy/>
//运行结果:可以看出:
//就是在代理对象执行方法的时候,把所有的增强方法都执行了一遍
环绕前
签名是:void com.changGe.li.pojo.User.add()
这是before方法
add实现了
这是after方法1
这是after方法
环绕后
返回值是:null

整合MyBatis

Access denied for user ‘’@‘localhost’ (using password: YES)错误解决方法

(7条消息) MySQL Access denied for user ‘root‘@‘localhost‘ (using password: YES/NO) 的原因以及解决方案_花花花菜的博客-优快云博客

加上mybatis-spring和spring-jdbc的依赖

基本的mybatis和mysql的依赖,可能会用到的都可以导入一遍

<!--核心依赖,让mybatis支持spring整合-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.6</version>
</dependency>

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

<!--没有mysql的依赖,可能会报错:无法加载driver驱动程序-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.27</version>
</dependency>

<!--没有这个,用不了Resources等mybatis的类-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.7</version>
</dependency>
package com.changGe.li.pojo;

import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class Student {
  private String id;
  private String name;
  private int tid;
}
package com.changGe.li.pojo;

import java.util.List;

public interface StudentMapper {

  List<Student> getStudentList();

}

jdbc.properties

driver=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC

mybatis-config.xml

<?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.changGe.li.pojo"/>
    </typeAliases>
    
    <mappers>
        <package name="com/changGe/li/pojo/StudentMapper.xml"/>
    </mappers>

</configuration>

StudentMapper.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.changGe.li.pojo.StudentMapper">

    <select id="getStudentList" resultType="student">
        select * from student;
    </select>

</mapper>

beans.xml整合mybatis配置

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


    <!--引入外部properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

	<!--加载当前项目及所依赖的jar包下的所有的.properties文件,
	    同时永不使用系统属性,避免和系统属性中的username冲突-->
	<!--    <context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>-->


<!-------------------------------------------------------------------------------------->

    <!--spring自带的jdbc:DriverManagerDataSource
        mybatis-config.xml中的环境(environments)就不需要了
    -->
    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${driver}"/>
        <property name="url" value="${url}"/>
        <!--这里写username就会报错:Access denied for user(拒绝用户访问) '林木'@'localhost' (using password: YES)-->
        <property name="username" value="${password}"/>
        <property name="password" value="${password}"/>
    </bean>

    <!--用Spring的类,来配置sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--引入环境-->
        <property name="dataSource" ref="datasource"/>

        <!--引入mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/changGe/li/pojo/StudentMapper.xml"/>
    </bean>

</beans>

测试类

import com.changGe.li.pojo.Student;
import com.changGe.li.pojo.StudentMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class SpringTest {

  @Test
  public void sprintTest(){
    try {
      ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");
      SqlSessionFactory sqlSessionFactory = application.getBean("sqlSessionFactory", SqlSessionFactory.class);

      /*//这里跟随主配置文件而加载spring的配置文件
      InputStream resourceAsStream = Resources.getResourceAsStream("beans.xml");
      SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);*/

      SqlSession sqlSession = sqlSessionFactory.openSession();

      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
      List<Student> studentList = mapper.getStudentList();

      for (Student student : studentList) {
        System.out.println(student.toString());
      }

    } catch (Exception e) {
      e.printStackTrace();
    }

  }

}

SqlSessionTemplete

SqlSessonTemplete(SqlSession模板),可以通过SqlSessionFactroy直接构建SqlSession对象

<!--用Spring的类,来配置sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <!--引入环境-->
  <property name="dataSource" ref="datasource"/>

  <!--引入mybatis配置文件-->
  <property name="configLocation" value="classpath:mybatis-config.xml"/>
  <property name="mapperLocations" value="classpath:com/changGe/li/pojo/StudentMapper.xml"/>
</bean>

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
  <!--这个没有set方法,只能用构造函数-->
  <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
//sqlSessionTemplete就是一个qlSession
SqlSession sqlSession = application.getBean("sqlSessionTemplate", SqlSession.class);

StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudentList();

for (Student student : studentList) {
  System.out.println(student.toString());
}

SqlSessionTemplete是线程安全的,他的构造方法的参数可以用sqlsessionfactroy

//SqlSessionTemplete构造器源码
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

在这里我们可以把spring.xml再次拆分,变成一个专注配置,一个专注管理bean.

bases.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:property-placeholder location="classpath:jdbc.properties"/>


    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${password}"/>
        <property name="password" value="${password}"/>
    </bean>

</beans>

beans.xml专注于bean管理,通过导入bases.xml实现整合mybatis

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


  	<!--导入bases.xml整合mybatis-->
    <import resource="bases.xml"/>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="datasource"/>

        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--当mapper.xml和mapper接口不在同包结构,或不能自动找到时,配置这个属性.不然最好不要配置-->
        <property name="mapperLocations" value="classpath:com/changGe/li/pojo/StudentMapper.xml"/>
    </bean>

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>

</beans>

SqlSessionDaosupport:SqlSessionDao支持

package com.changGe.li.pojo;

import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper{

    //SqlSessionDaoSupport的源码是:return this.sqlSessionTemplate;
    private SqlSession sqlSession = this.getSqlSession();

    private StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

    @Override
    public List<Student> getStudentList() {

        return mapper.getStudentList();
    }

}

直接测试会报错:NullPointException

@Test
public void sprintTest(){

  StudentMapper studentMapper = new StudentMapperImpl();

  List<Student> studentList = studentMapper.getStudentList();

  for (Student student : studentList) {
    System.out.println(student.toString());
  }

}

因为SqlSessionDaoSupport,底层需要注入一个SqlSessionFactroy

getSqlSession时会调用他自己的sqlSessionTemplate,但是这个变量初始并没有赋值

public final SqlSessionFactory getSqlSessionFactory() {
  return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
}
private SqlSessionTemplate sqlSessionTemplate;

他希望子类是通过传入SqlSessionFactroy的方式,创建一个SqlSessionTemplate

protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  return new SqlSessionTemplate(sqlSessionFactory);
}

正确的用法应该是这样

package com.changGe.li.pojo;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper{


    @Override
    public List<Student> getStudentList() {

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

        //获取sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = applicationContext.getBean("sqlSessionFactory", SqlSessionFactory.class);

        SqlSessionTemplate sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);

        //获取mapper
        StudentMapper mapper = sqlSessionTemplate.getMapper(StudentMapper.class);

        return mapper.getStudentList();
    }

}

或者直接在beans.xml中配置sqlSessionFactroy对象

<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/changGe/li/pojo/StudentMapper.xml"/>
</bean>

<bean id="studentMapperImpl" class="com.changGe.li.pojo.StudentMapperImpl">
  <!--Spring自动知道我们需要一个sqlSessionFactory对象-->
  <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

Spring会自动注入SqlSessionDaoSupport需要的SqlSessionFactroy

我们也可以在实现接口的时候,加入我们的代码了

public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper{

    @Override
    public List<Student> getStudentList() {
        StudentMapper mapper = this.getSqlSession().getMapper(StudentMapper.class);

        //这里可以变成其他业务代码
        System.out.println("Hello Spring");
      
        return mapper.getStudentList();
    }

}
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

StudentMapper studentMapper = applicationContext.getBean("studentMapperImpl", StudentMapperImpl.class);

List<Student> studentList = studentMapper.getStudentList();

for (Student student : studentList) {
  System.out.println(student.toString());
}
//运行结果
Hello Spring
Student(id=1, name=天才第二步, tid=1)

声明式事务

Spring配置的dataSource,默认关闭事务(MyBatis在配置环境时,必须配置transactionManager type=“jdbc”/>,默认是开启事务的)

所以下面的这些代码,即使有明显的异常,最终inster也会成功.

Student实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student {
  private String id;
  private String name;
  private int tid;
}

StudentMapper

public interface StudentMapper {

  int insertStudent(Student student);
  int deleteStudentById(@Param("id")String id);

  void query();
}

StudentMapper实现类,运行insertStudent方法时,必定产生一个异常

public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper{

    @Override
    public int insertStudent(Student student) {
        StudentMapper mapper = this.getSqlSession().getMapper(StudentMapper.class);
        int i = mapper.insertStudent(student);

      	//java.lang.ArithmeticException: / by zero
        i = 1 / 0;

        return i;
    }

    @Override
    public int deleteStudentById(String id) {
        StudentMapper mapper = this.getSqlSession().getMapper(StudentMapper.class);

        return mapper.deleteStudentById(id);
    }
  
 		@Override
    public void query() {}

}

StudentMapper.xml

<insert id="insertStudent">
  insert into student values(#{id},#{name},#{tid})
</insert>

<delete id="deleteStudentById">
  delete from student where id = #{id};
</delete>
  
<select id="query"></select>

测试

StudentMapper studentMapper = applicationContext.getBean("studentMapperImpl", StudentMapper.class);

Student student = new Student("14","路飞",1);
studentMapper.insertStudent(student);

studentMapper.deleteStudentById("1");

最后insert还是被执行了

执行


这时我们就需要配置事务是为了:保证ACID原则

<!--配置声明式事务管理器,并引用环境-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="datasource"/>
</bean>

导入aop和tx约束

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans 		   http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
">
<!--配置事务通知-->
<tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
  <tx:attributes>
    <!--所有方法配置事务和传播方式,
            监听到异常后必须回滚,默认是监听RuntimeException-->
    <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.ArithmeticException"/>

    <!--必须大写,小写报错:值 'required' 不具有面有效性。它必须是来自枚举的值-->
    <tx:method name="insertStudent" propagation="REQUIRED" isolation="READ_COMMITTED"/>

    <tx:method name="deleteStudentById" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="query" read-only="true"/>
  </tx:attributes>

</tx:advice>


<!--配置事务切入-->
<aop:config>
  <!--pojo包下所有方法,都可以被切入-->
  <aop:pointcut id="point" expression="execution(* com.changGe.li.pojo.*.*(..))"/>
  <!--把事务切入进来-->
  <aop:advisor advice-ref="transactionInterceptor" pointcut-ref="point"/>
</aop:config>

spring事务传播属性

Spring事务的传播:PROPAGATION_REQUIRED - 神只吃苹果 - 博客园 (cnblogs.com)

在 spring的 TransactionDefinition接口中一共定义了六种事务传播属性:

PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择
PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)


事务的隔离级别

(7条消息) spring 中 isolation 和 propagation 详解_饥饿小猪的博客-优快云博客

  1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应
  2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
  3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
  4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

helloses

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值