Spring

简介

Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。

然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。

  • 2002,首次推出了Spring框架的雏形:interface21框架。
  • Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,与2004年3月24日发布了1.0正式版!
  • Rod Johnson,Spring框架的创始人,同时也是SpringSource的联合创始人。Spring是面向切面编程(AOP)和控制反转(IoC)的容器框架。
  • Spring的基本理念:使现有的技术更加容易地使用,其本身就是一个大杂烩,整合了现有的技术框架。
  • SSH:Struts2+Spring+Hibernate
  • SSM:SpringMVC+Spring+Mybatis

官网:https://spring.io/

GitHub:https://github.com/spring-projects/spring-framework/find/main

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

优点:

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

缺点:

  • 因为发展了很久,导致现在糅杂了太多的技术与框架,使得配置变得十分繁琐,人称配置地域!

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

Spring7大模块:

扩展

  • Spring Boot

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

因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring和SpringMVC。

1、IOC理论推导

传统Dao层与服务层:

public interface UserDao {void getUser();
}
public class UserDaoImpl implements UserDao {@Overridepublic void getUser() {System.out.println("默认调用UserDao接口!");}
}
public interface UserService {void getUser();
}
public class UserServiceImpl implements UserService {UserDao userDao = new UserDaoImpl();@Overridepublic void getUser() {userDao.getUser();}
}

此时,用户实际是调用了Service层的业务,但当用户需求有变动时,例如此时用户并不想要调用默认的UserDao接口,而是想要调用特殊的接口去链接Oracle数据库,解决方法可以是重新写一个UserDao的实现类,继承接口后重写方法。但是在代码量十分庞大的情况下,这种措施无疑是最坏的做法。

假设此时有一个UserDao实现类:

public class UserDaoOracleImpl implements UserDao {@Overridepublic void getUser() {System.out.println("Oracle调用UserDao接口!");}
}

而这次,并不直接创建一个新的业务实现类,而是在原有的业务实现类进行一些修改:

public class UserServiceImpl implements UserService {private UserDao userDao;// 利用Set进行动态实现值的注入!public void setUserDao(UserDao userDao) {this.userDao = userDao;}@Overridepublic void getUser() {userDao.getUser();}
}

可以发现,以上实例将UserDao作为业务层的属性,并重写了set方法,于是在测试类中,可以这样进行测试:

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

运行的结果并无二异,但此时可以通过setUserDao方法,可以自行选择走哪个UserDao,代码虽然看上去没有进行多大的变动,但是逻辑性却已经发生了翻天覆地的变化。

  • 在更改之前:程序是主动创建的对象,控制权在程序员手中!
  • 使用了Set注入之后:程序不再具有主动性,而是变成了被动接受的对象!

也就是说,这种思想已经从本质上解决了问题,程序员不再需要去管理对象的创建,系统的耦合性大大降低,可以更加专注于业务的实现!这也就是IOC的原型!

1.1 IOC的本质

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

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

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

1.2 HelloSpring

1、准备元数据:

@Data
public class Hello {private String str;}

2、编写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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- id相当于一个变量名, class为需要实例化的对象-->
<!-- property为对象中的属性,而value相当于为属性设置了一个值 -->
<!-- 使用Spring来创建对象,在Spring中这些都成为Bean --><bean id="Hello" class="com.atayin.pojo.Hello"><property name="str" value="Spring"/></bean></beans>

3、实例化容器

public class HelloTest {public static void main(String[] args) {//       获取spring的上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//       取出对象Hello hello = (Hello) context.getBean("hello");System.out.println(hello);}
}

思考:

  • Hello 对象是谁创建的 ?

    hello 对象是由Spring创建的。

  • Hello 对象的属性是怎么设置的 ?

    hello 对象的属性是由Spring容器设置的。

这个过程就叫控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的 .

  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 。

依赖注入 : 就是利用set方法来进行注入的。假设实体类没有set方法,那么配置文件会报错!

IOC是一种编程思想 , 由主动的编程变成被动的接收,也可以通过newClassPathXmlApplicationContext去浏览一下底层源码。

所谓的IoC,对象由Spring 来创建,管理,装配 !

1.3 IOC创建对象的方式

1、IOC创建对象的默认方式为使用无参构造方法创建对象,当实体类不存在无参构造时,会报错!

<bean id="user" class="com.atayin.pojo.User"><property name="name" value="Ayin"/>
</bean>

2、假设需要使用有参构造方法创建对像,一共有以下三种方式:

  • 使用下标赋值:
<bean id="user" class="com.atayin.pojo.User"><constructor-arg index="0" value="Ayin"/>
</bean>
  • 使用类型赋值:这种方式并不建议使用,当构造方法拥有数个相同类型的参数时,使用该方法将会出错!
<bean id="user" class="com.atayin.pojo.User"><constructor-arg type="java.lang.String" value="Ayin"/>
</bean>
  • 使用参数名赋值:
<bean id="user" class="com.atayin.pojo.User"><constructor-arg name="name" value="Ayin"/>
</bean>

2、Spring配置

别名

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

添加别名之后,可以通过别名获取到这个对象:

ApplicationContext context = new ClassPathXmlApplicationContext();
context.getBean("ksjfksdjaf");

当然,并不是取了别名之后,原本的属性名就失效了,调用原属性名依然可以取到这个对象!

Bean

<bean id="user" class="com.atayin.pojo.User" name="user2,u2"></bean>

在bean标签中,name属性也可以取别名,也可以起多个别名。

import

这个标签可以将多个配置文件,导入合并为一个applicationConfig.xml!

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

3、依赖注入

依赖:bean对象的创建依赖于容器!

注入:bean对象中所有的属性,由容器来注入!

映射键或值的值或设置值也可以是以下任一元素:

bean | ref | idref | list | set | map | props | value | null

环境搭建

复杂对象:

@Data
public class Address {private Address address;
}

真实测试对象:

@Data
public class Student {private String name;private Address address;private String[] book;private List<String> hobbys;private Map<String, String> card;private Set<String> games;private String wife;private Properties info;
}

注入:

       <bean id="address" class="com.atayin.pojo.Address"><property name="address" value="北京"/></bean><bean id="student" class="com.atayin.pojo.Student">
<!--            1、普通值注入,value--><property name="name" value="Ayin"/>
<!--            2、Bean注入,ref--><property name="address" ref="address"/>
<!--            数组--><property name="book" ><array><value>红楼梦</value><value>西游记</value><value>三国演义</value></array></property>
<!--            List--><property name="hobbys"><list><value>唱</value><value>跳</value></list></property>
<!--            Map--><property name="card"><map><entry key="身份证" value="11337383846193885"/></map></property>
<!--            Set--><property name="games"><set><value>LOL</value><value>COC</value></set></property>
<!--            null--><property name="wife"><null></null></property>
<!--            Properties--><property name="info"><props><prop key="driver">mysql.Driver</prop><prop key="url">#</prop><prop key="username">root</prop><prop key="password">root</prop></props></property></bean>

命名空间注入:

p命名空间:可以直接注入属性的值(properties)

xmlns:p="http://www.springframework.org/schema/p"
<?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:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user" class="com.atayin.pojo.User" p:name="Ayin" p:age="18"/>
</beans>

c命名空间:通过构造器注入(constructor)

xmlns:c="http://www.springframework.org/schema/c"
<?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:c="http://www.springframework.org/schema/c"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user" class="com.atayin.pojo.User" c:name="Ayin" c:age="18"/></beans>

需要注意的是,这两种命名空间并不能直接使用,需要导入xml!

4、Bean的作用域

4.1 The Singleton Scope

单例模式,也是Spring的默认机制!

<bean id="user" class="com.atayin.pojo.User" scope="singleton"/>
public void userTest() {ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");User user = context.getBean("user", User.class);User user2 = context.getBean("user", User.class);System.out.println(user == user2);
}

返回:true

4.2 The Prototype Scope

原型模式,每次从容器中get的时候,每次都会产生一个新的对象!

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
public void userTest() {ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");User user = context.getBean("user", User.class);User user2 = context.getBean("user", User.class);System.out.println(user == user2);
}

返回:false

注:其他作用域一般只会在web开发中使用

5、Bean的自动装配

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

在Spring中有三种装配方式:

1、 在xml中显式配置

2、在Java中显式配置

3、隐式的自动装配Bean

测试:

假设此时一共有两个实体类,分别为Cat和Dog,

public class Cat {public void shout() {System.out.println("meow");}
}
public class Dog {public void shout() {System.out.println("wow");}
}

People实体类中,属性包含以上两个类:

public class People {private Cat cat;private Dog dog;private String name;
}

未设置自动装配的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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="cat" class="com.atayin.pojo.Cat"/><bean id="dog" class="com.atayin.pojo.Dog"/><bean id="people" class="com.atayin.pojo.People"><property name="dog" ref="dog"/><property name="cat" ref="cat"/><property name="name" value="Ayin"/></bean>
</beans>

设置了自动装配的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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="cat" class="com.atayin.pojo.Cat"/><bean id="dog" class="com.atayin.pojo.Dog"/><bean id="people" class="com.atayin.pojo.People" autowire="byName"><property name="name" value="Ayin"/></bean>
</beans>

可以发现,前者省去了两行设置属性的代码。

byName: 会自动在容器的上下文中寻找,是否有与本身实体类中的set方法所对应的Bean的ID!例如:People实体类中有一个set方法为setDog(),设置了自动装配之后,会自动寻找是否有名称为Dog的实体类。

注意:如果在xml文件中,id属性的值修改了和实体列不符的值,将会报错!

byType: 会自动在容器的上下文中寻找,是否有与本身实体类中的set方法对象属性类型相同的Bean的ID!但需要注意的是,使用该方法需要保证实体类作为属性是全局唯一的。而且使用该方法,甚至可以省去bean标签中的id属性!

5.1 注解实现自动装配

1、导入约束

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

2、配置对注解的支持

<context:annotation-config/>

3、beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><bean id="cat" class="com.atayin.pojo.Cat"/><bean id="dog" class="com.atayin.pojo.Dog"/><bean id="people" class="com.atayin.pojo.People"/><context:annotation-config/></beans>

4、实体类

@Data
public class People {@Autowiredprivate Cat cat;@Autowiredprivate Dog dog;private String name;
}

当使用@Autowired之后,实体类甚至可以不再编写set方法,但前提是自动装配的属性存在于IOC(Spring)容器,而且符合命名!

假如@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解完成的时候,我们可以使用@Qualifier(value=“xxx”)去配置@Autowired的使用,去配置一个唯一的bean对象注入!

@Autowired和@Resource的相同点:

  • 都是用来自动装填的,都可以放在属性字段上。

不同点:

  • 执行顺序不同。

    • @Autowired通过byType的方式实现,而且必须要求对应对象存在。
    • @Resource默认通过byName的方式实现,如果找不到对应属性名,则通过byType实现!如果两个都找不到的情况下,就报错!

6、注解开发

在Spring4之后,要使用注解开发,必须要导入AOP的包!

这也是为什么一开始建议导入spring-webmvc的Maven,因为它会自动把下面所关联的包都进行导入!

而且,要使用注解,也必须导入关于注解的支持!

<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd">
<!--  扫描指定包下的注解,使其生效  --><context:component-scan base-package="com.atatyin.pojo"/><context:annotation-config/>
</beans>

@Component

// @Component组件等价于 <bean id="user" class="com.atatyin.pojo.User"/>
@Component
public class User {public String name = "Ayin";
}
@Test
public void UserTest() {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");User user = context.getBean("user", User.class);System.out.println(user.name);
}

@Component:组件,放在类上说明该类已经被Spring管理了,也就是说,它已经生成了一个Bean,并将它放进了Spring的容器中!

@Value

@Component
public class User {public String name;// @Value组件等价于 <property name="name" value="Ayin"/>@Value("Ayin")public void setName(String name) {this.name = name;}
}

@Value:在一些较为简单的情况下可以适用该注解来为属性注入值!

@Component衍生注解

@Component有几个衍生注解,在web开发中,会按照MVC三层架构来分层!

  • dao(@Repository)
  • service(@Service)
  • web(@Controller)

以上四个注解的功能其实都是一样的,都是代表着将某个类注册到Spring中装配Bean!

@Scope

@Scope("prototype")
public class User {public String name;}

该注解可以将作用域设置为指定形式!

关于XML与注解:

  • XML更加万能,适用于任何场所!而且维护起来简单方便!
  • 注解不是自己类就是用不了,维护相当复杂!

XML与注解的最佳实践:

  • XML用来管理Bean!
  • 注解只负责完成属性的注入!
  • 在使用注解的过程中,必须要让注解生效!注意开启注解支持!

7、使用Java的方式配置Spring

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

实体类:

@Data
@Component
public class User {@Value("Ayin")public String name;
}

配置类:

// @Configuration代表着一个配置类,相当于一个beans.xml
// 分析该注解的源码可以发现,该注解也使用了@Component,也就是说@Configuration也会被Spring托管放入容器之中,因为它本身也就是一个@Component
@Configuration
@ComponentScan("com.atayin.pojo")
// 假设还有一个配置类,可以通过@import将两个类进行融合
@import(UserConfig2.class)
public class UserConfig {// 注册一个Bean,相当于一个bean标签// 方法名相当于bean标签中的id属性// 返回值相当于bean标签中的class属性,也就是需要返回注入到bean中的对象@Beanpublic User getUser() {return new User();}
}

测试类:

public class MyTest {public static void main(String[] args) {// 如果完全使用了配置类方式去做,那么只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载!ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);User user = context.getBean("getUser", User.class);System.out.println(user.getName());}
}

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

8、代理模式

为什么要学习代理模式?

  • 因为这是SpringAOP的底层原理

代理模式分类:

  • 静态代理模式
  • 动态代理模式

8.1 静态代理

静态代理,可以简单理解为找一个对象去帮你完成某一件事情。例如:此时小明需要租房,那他可以先找到租房中介,让中介去找房东,并将房子租给小明,此时小明并没有直接和房东进行交接,而是和中介进行交接。

// 出租(接口)
public interface Rent {public void rent();
}
// 房东(真实角色)
public class Host implements Rent {@Overridepublic void rent() {System.out.println("房东需要出租房子!");}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
// 中介商代理(代理角色)
public class Proxy implements Rent {private Host host;@Overridepublic void rent() {host.rent();}
}
// 客户通过中介租房(客户)
public class Client {public static void main(String[] args) {Host host = new Host();Proxy proxy = new Proxy(host);proxy.rent();}
}

静态代理模式中,一共有以下四种角色:

  • 抽象角色:一般会使用接口或者抽象类来表示。
  • 真实角色:被代理的角色。
  • 代理角色:代理真实角色,一般在这之上会做一些附属操作!
  • 客户:访问代理对象的角色。

优点:

  • 可以使得真实角色更加纯粹,将事务全权交给代理角色处理!

  • 实现了业务的分工!

  • 公共业务需要扩展时,方便集中管理。

缺点:

  • 一个真实角色就会需要一个代理角色,代码量会翻倍,开发效率会被降低!

8.2 动态代理

  • 动态代理和静态代理的角色一致!
  • 动态代理的代理类是动态生成的,而不是程序员写的!
  • 动态代理也被分为两大类:基于接口的动态代理,和基于类的动态代理!
    • 基于接口:JDK动态代理
    • 基于类:cglib
    • Java字节码实现:javasist

在了解动态代理之前,需要了解两个类:Proxy(代理)和InvocationHandler(调用处理程序)。

测试:

此时使用静态代理中已经写好的Rent接口类以及Host实体类,作为抽象角色和真实角色。

编写InvocationHandler(动态生成代理):

public class ProxyInvocationHandler implements InvocationHandler {// 处理代理实例,并返回结果// 被代理的接口private Rent rent;public void setRent(Rent rent) {this.rent = rent;}// Proxy提供了创建动态代理类和实例的静态方法!public Object getProxy() {return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 动态代理的本质,就是使用反射机制实现!Object invoke = method.invoke(rent, args);return invoke;}
}

需要注意的是,Proxy提供了创建动态代理类和实例的静态方法,所以刻意直接调用newProxyInstance()方法,方法的参数如下:

public static Object newProxyInstance(ClassLoader loader,@NotNull Class<?>[] interfaces,@NotNull reflect.InvocationHandler h)

第一个参数为动态代理生成类(也就是继承了InvocationHandler接口的类)的类加载器,第二个参数为抽象角色(接口),第三个参数为动态代理生成类本身(this)!

public class Client {public static void main(String[] args) {ProxyInvocationHandler pih = new ProxyInvocationHandler();// 真实角色Host host = new Host();// 代理角色pih.setRent(host);Rent proxy = (Rent) pih.getProxy();proxy.rent();}
}

通过测试可以发现,即使没有写代理角色(Proxy)类,但程序仍然执行成功,说明此时的代理类为动态生成!

动态代理的优点:

  • 具有静态代理的特点。

    • 一个动态代理类代理的是一个接口,一般对应的就是一类业务。
    • 一个动态代理类可以代理多个类,只要实现了同一个接口即可!

9、AOP

什么是AOP?

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

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP在Spring中的作用

  • 提供声明式服务!
  • 允许用户自定义切面!

概念定义

  • 横向关注点:跨越应用程序多个模块的方法或功能。简单来说,与业务逻辑无关,但也是需要关注的部分,就是横向关注点。例如:日志、安全、缓存、事务等等。
  • 切面(ASPECT):横向切面点被模块化的特殊对象。也就是说,它是一个类。
  • 通知(Advice):切面必须要去完成的工作,相当于类中的方法。
  • 目标(Target):被通知的对象。
  • 代理(Proxy):向目标对象应用通知后所创建的对象。
  • 切入点(PointCut):切面通知执行地点的定义。
  • 连接点(JoinPoint):与切入点相匹配的执行点。

## 9.1 使用Spring实现AOP

在准备工作之前,需要导入aop所需要的包!

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.8</version>
</dependency>

方式一:使用Spring的API接口

UserService:

public interface UserService {public void add();public void delete();public void update();public void select();
}

Log:

public class Log implements MethodBeforeAdvice {@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行");}
}

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!--  配置aop:需要导入aop的约束  -->
<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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="userService" class="com.atayin.service.UserServiceImpl"/><bean id="log" class="com.atayin.log.Log"/><bean id="afterLog" class="com.atayin.log.AfterLog"/><aop:config>
<!-- 表达式(expression); execution(修饰词, 返回值, 类名, 方法名, 参数)--><aop:pointcut id="pointcut" expression="execution(public * com.atayin.service.UserServiceImpl.*(..))"/>(..))"/>
<!--  执行环绕增加--><aop:advisor advice-ref="log" pointcut-ref="pointcut"/><aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/></aop:config>
</beans>

注意:需要导入aop的约束,否则无法生效!

测试类:

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

注意:一般而言,动态代理所代理的类都是接口类!

切点表达式

execution([修饰符] 返回值类型 包名.类名.方法名(参数))
  • 修饰符可以省略
  • 返回值类型,包名,类名,方法名都可以用*代表任意
  • 包名与类名直间一个点“.”代表当前包下的类,“…”代表当前包下及子包下的所有类
  • 参数列表“…”代表任意个数、任意类型的参数

例如:

execution(void com.lagou.service.UserService.add())
execution(* com.lagou.service.UserService.add())
execution(* com.lagou.service.UserService.*())
execution(* com.lagou.service.*.*(..))
execution(* com.lagou.service..*.*(..))

方式二:自定义类

自定义切入点类:

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

配置文件:

<bean id="diy" class="com.atayin.diy.DiyPointCut"/><aop:config><!-- 自定义切面 --><aop:aspect ref="diy"><!-- 切入点--><aop:pointcut id="point" expression="execution(public * com.atayin.service.UserServiceImpl.*(..))"/><aop:before method="before" pointcut-ref="point"/><aop:after method="after" pointcut-ref="point"/></aop:aspect>
</aop:config>

方式三:使用注解实现

自定义切入点类:

@Component
@Aspect // 标注这个类为一个切面类!
public class AnnotationPointCut {@Before("execution(public * com.atayin.service.UserServiceImpl.*(..))")public void before() {System.out.println("方法执行前!");}
}

注意:此时我们使用了注解来实现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"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:annotation-config/><context:component-scan base-package="com.atayin"/><!-- 开启注解支持 --><aop:aspectj-autoproxy/>
</beans>

注意:使用注解来实现AOP,也需要在配置文件中开启对注解的支持!

<aop:aspectj-autoproxy/>

值得一提的是,这里面有一个名为proxy-target-class的属性,默认为false,会使用JDK方式去实现,设置为true后,会使用cglib去实现!

关于@Before、@After、@Around的执行顺序:

@Component
@Aspect // 标注这个类为一个切面类!
public class AnnotationPointCut {@Before("execution(public * com.atayin.service.UserServiceImpl.*(..))")public void before() {System.out.println("方法执行前!");}@After("execution(public * com.atayin.service.UserServiceImpl.*(..))")public void after() {System.out.println("方法执行后!");}@Around("execution(public * com.atayin.service.UserServiceImpl.*(..))")public void around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕前!");joinPoint.proceed(); // 执行方法System.out.println("环绕后!");System.out.println(joinPoint.getSignature());}
}

通过测试结果可以得知,优先执行@Around,然后执行joinPoint.proceed()方法,执行后会触发@Before,该方法执行完成后,会执行@After,随后将@Around剩余的部分执行完毕!

10、整合MyBatis

10.1 Mybatis测试

在进行Mybatis的测试之前,先将未来实例所需要的jar包进行导入!

1、Spring整合Mybatis所需jar包

  • junit
  • mybatis
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.9</version>
</dependency>
  • mysql数据库
<!-- spring操作数据库还需要一个Spring-jdbc -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.16</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version>
</dependency>
  • spring相关
  • aop织入
  • mybatis-spring
<!-- spring与mybatis整合所需的包 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.7</version>
</dependency>

2、编写Mybatis核心配置文件

mybatis-config.xml:

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件-->
<configuration><typeAliases><package name="com.atayin.pojo"/></typeAliases><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT"/><property name="username" value="root"/><property name="password" value="12345"/></dataSource></environment></environments><mappers><package name="com.atayin.dao"/></mappers>
</configuration>

3、编写工具类

MybatisUtils工具类:

public class MyBatisUtils {public static SqlSessionFactory sqlSessionFactory;static {try {// 使用Mybatis的第一步,获取SqlSessionFactory对象String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);} catch (IOException e) {e.printStackTrace();}}// 既然已经有了SqlSessionFactory对象,也就是说,已经建造起了存放SqlSession对象的工厂// 接下来就是从这个工厂里,拿出所需要的SqlSession对象即可public static SqlSession getSqlSession() {// SqlSession对象中包含面向数据库执行SQL命令所需的所有方法return sqlSessionFactory.openSession();}
}

4、编写实体类、接口类、以及对应接口的Mapper.xml

User实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private int id;private String name;private String pwd;
}

UserMapper与Mapper.xml:

public interface UserMapper {List<User> select();
}
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.atayin.dao.UserMapper"><!--SQL查询语句--><select id="select" resultType="com.atayin.pojo.User">select * from user;</select>
</mapper>

5、测试

@Test
public void select() {SqlSession sqlSession = MyBatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> list = mapper.select();for (User user : list) {System.out.println(user);}sqlSession.close();
}

测试结果理应将User表中的每一行进行输出!

10.2 Spring-Mybatis

什么是 MyBatis-Spring?

MyBatis-Spring 会将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

知识基础

在开始使用 MyBatis-Spring 之前,需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。

MyBatis-Spring 需要以下版本:

MyBatis-Spring MyBatis Spring Framework Spring Batch Java
2.0 3.5+ 5.0+ 4.0+ Java 8+
1.3 3.4+ 3.2.2+ 2.1+ Java 6+

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

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

1、配置DataSource:

<!--Datasource:使用Spring的数据源替换mybatis的配置(c3p0、dbcp、druid)以下使用的是Spring提供的JDBC:org.springframework.jdbc.datasource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="username" value="root"/><property name="password" value="12345"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT"/><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>

2、SqlSessionFactory:

<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource" /><!--        绑定mybatis的配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"/><!--        绑定Mapper--><property name="mapperLocations" value="classpath:com/atayin/dao/*.xml"/>
</bean>

3、SqlSession:

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替代码中已经在使用的 SqlSessionSqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。

当调用 SQL 方法时(包括由 getMapper() 方法返回的映射器中的方法),SqlSessionTemplate 将会保证使用的 SqlSession 与当前 Spring 的事务相关。 此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。另外,它也负责将 MyBatis 的异常翻译成 Spring 中的 DataAccessExceptions

由于模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,应该总是SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法(因为它并没有set方法,所以无法用set注入)的参数来创建 SqlSessionTemplate 对象。

<!--    SqlSessionTemplate:可以当成Mybatis中所使用的SqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"><!--            由于该模版没有set方法,所以只能使用构造器注入值--><constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

4、增加接口实现类:

public class UserMapperImpl implements UserMapper{private SqlSessionTemplate sqlSession;public void setSqlSession(SqlSessionTemplate sqlSession) {this.sqlSession = sqlSession;}@Overridepublic List<User> select() {UserMapper mapper = sqlSession.getMapper(UserMapper.class);return mapper.select();}
}

5、将接口实现类注入Spring中:

<import resource="beans.xml"/>
<bean id="userMapper" class="com.atayin.dao.UserMapperImpl"><property name="sqlSession" ref="sqlSession"/>
</bean>

注意:这个步骤可以另起一个xml文件为applicationConfig.xml,这样关于Spring-Mybatis的配置文件就这样定下来了,并不需要再往配置文件中增添内容。

6、测试文件

public class MyBatisTest {@Testpublic void select() {ApplicationContext context = new ClassPathXmlApplicationContext("applicationConfig.xml");UserMapper userMapper = context.getBean("userMapper", UserMapper.class);List<User> list = userMapper.select();for (User user : list) {System.out.println(user);}}
}

如何理解以上步骤?

  • 传统的Mybatis对数据库进行增删改查,需要经过以下步骤:

    • 获得数据源。
    • 获得SqlSessionFactory。
    • 通过SqlSessionFactory获得SqlSession。
    • SqlSession通过getMapper()方法获得UserMapper。
    • 调用接口写好的方法,在Mapper.xml中寻找执行该方法的SQL语句,并执行。
    • 返回结果。
  • 整合Mybatis之后,完成的步骤和以上并无太大区别:

    • 使用Spring提供的类获得数据源并注入为bean。
    • 使用SqlSessionFactoryBean获得SqlSessionFactory并注入为bean,并绑定mybatis的核心配置文件以及Mapper.xml配置文件。
    • 通过SqlSessionFactory,使用SqlSessionTemplate获得SqlSession并使用构造器注入为bean。
    • 编写接口实现类,设置一个为SqlSessionTemplate的属性,并为其附加set方法(bean的注入离不开set方法)!
    • 当测试程序调用接口类方法时,注入为bean的SqlSession会作为参数传至接口实现类,并通过getMapper()方法获取UserMapper——

10.3 SqlSessionDaoSupport

SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。调用 getSqlSession() 方法会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法,就像下面这样:

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{@Overridepublic List<User> select() {return getSqlSession().getMapper(UserMapper.class).select();}
}

在这个类里面,通常更倾向于使用 MapperFactoryBean,因为它不需要额外的代码。但是,如果需要在 DAO 中做其它非 MyBatis 的工作或需要一个非抽象的实现类,那么这个类就很有用了。

继承该类主要是省去了将SqlSession设置为属性,并为其注入值的过程,也就是说,在之前所述的步骤中,使用SqlSessionTemplate来创建SqlSession可以进行省去。

SqlSessionDaoSupport 需要通过属性设置一个 sqlSessionFactorySqlSessionTemplate。如果两个属性都被设置了,那么 SqlSessionFactory 将被忽略。

假设类 UserMapperImplSqlSessionDaoSupport 的子类,可以编写如下的 Spring 配置来执行设置:

<bean id="userMapper" class="com.atayin.dao.UserMapperImpl"><property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

可以发现,在注入UserMapper的bean标签中,属性的参数从SqlSession变为了SqlSessionFactory,因为SqlSession已经交付给SqlSessionDaoSupport进行创建,而翻阅该类源码可以发现,它需要一个SqlSessionFactory作为参数。

11 声明式事务

事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。

关于事务,可以记住这样一句话:要么都成功,要么都失败!

事务在项目开发中十分重要,因为它涉及到数据的一致性问题,不能马虎!

事务ACID原则:

  • 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。

  • 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

  • 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  • 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

测试事务:

假设此时接口类中共存在三种方法,分别是查找、插入、删除!

public interface UserMapper {List<User> select();// 增加一个用户int addUser(User user);// 删除一个用户int deleteUser(@Param("id") int id);
}

但是在Mapper类所对应的Mapper.xml中,故意将delete方法的SQL语句写错:

<insert id="addUser" parameterType="User">insert into mybatis.user(id, name, pwd) VALUES (#{id}, #{name}, #{pwd});
</insert><!--以下为错误的SQL语句-->
<delete id="deleteUser" parameterType="int">deletes from mybatis.user where id = #{id};
</delete>

在UserMapper的实现类中,创建对应的实现方法:

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{@Overridepublic List<User> select() {UserMapper mapper = getSqlSession().getMapper(UserMapper.class);User user = new User(6,"testAyin", "123456");mapper.addUser(user);mapper.deleteUser(5);return mapper.select();}@Overridepublic int addUser(User user) {return getSqlSession().getMapper(UserMapper.class).addUser(user);}@Overridepublic int deleteUser(int id) {return getSqlSession().getMapper(UserMapper.class).deleteUser(id);}
}

当测试类执行select方法时,也会执行addUser方法以及deleteUser方法。执行完成之后,不出意料,程序报错。

但是观察数据库可以发现,虽然程序进行报错,可是addUser方法仍然执行了,而deleteUser方法并没有执行,这明显不符合事务的原则,也就是”要么都成功,要么都失败!“

到目前为止,事务大概分为两种类型:

  • 声明式事务:使用AOP,代码是横切进去的,并不影响应用代码。
  • 编程式事务:需要在代码中,进行事务的管理。

编程式事务在代码中进行处理,简单来说就是在执行方法时增添try-catch,但是这种情况修改了原有的代码,所以也不建议使用该方式,而且,Spring也提供了声明式事务的配置,完全可以通过Spring来进行处理,而这些工作也只需要修改配置文件即可。

要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象:

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

配置事务的通知,也就是绑定需要配置事务的方法:

<!--    配置事务通知,结合AOP实现事务的切入-->
<tx:advice id="interceptor" transaction-manager="transactionManager"><!--        为方法配置事务--><tx:attributes><tx:method name="add" propagation="REQUIRED"/></tx:attributes>
</tx:advice>

注:在使用事务标签之前,需要导入tx:advice标签所需要的依赖!

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/txhttps://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

配置事务的切入:

<!--    配置事务的切入-->
<aop:config><aop:pointcut id="txPointCut" expression="execution(public * com.atayin.dao.*.*(..))"/><aop:advisor advice-ref="interceptor" pointcut-ref="txPointCut"/>
</aop:config>

运行测试程序之后可以看到,程序仍然报错,但是插入语句并没有执行!

扩展:在配置事务通知时,绑定方法会产生一个传播属性:

REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。

MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。

REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。


**配置事务的通知,也就是绑定需要配置事务的方法:**```xml
<!--    配置事务通知,结合AOP实现事务的切入-->
<tx:advice id="interceptor" transaction-manager="transactionManager"><!--        为方法配置事务--><tx:attributes><tx:method name="add" propagation="REQUIRED"/></tx:attributes>
</tx:advice>

注:在使用事务标签之前,需要导入tx:advice标签所需要的依赖!

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/txhttps://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

配置事务的切入:

<!--    配置事务的切入-->
<aop:config><aop:pointcut id="txPointCut" expression="execution(public * com.atayin.dao.*.*(..))"/><aop:advisor advice-ref="interceptor" pointcut-ref="txPointCut"/>
</aop:config>

运行测试程序之后可以看到,程序仍然报错,但是插入语句并没有执行!

扩展:在配置事务通知时,绑定方法会产生一个传播属性:

REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。

MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。

REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。

开发框架-Spring相关推荐

  1. java轻量级框架_轻量级的Java 开发框架 Spring

    Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development a ...

  2. Spring 应用开发框架 Spring Boot 2.3.0 最新版本发布

    Spring Boot 项目旨在简化创建产品级的 Spring 应用和服务.你可通过它来选择不同的 Spring 平台.可创建独立的 Java 应用和 Web 应用,同时提供了命令行工具来允许 'sp ...

  3. [开发框架]-Spring

    概述 Spring是分层的Java SE/EE应用full-stack(全栈)轻量级开源框架,以IoC和AOP为内核,提供了展现层Spring MVC 和持久层Spring JDBC以及业务层事务管理 ...

  4. 开发框架-Spring(2) - 注解篇

    1.Bean基础配置 Spring除了上一篇提到的xml配置文件进行配置之外,还可以使用注解方式进行配置,注解方式慢慢称为xml配置的替代方案.我们有了xml开发的经验,学习注解开发就方便了许多,注解 ...

  5. Spring为什么建议构造器注入?

    以下文章来源方志朋的博客,回复"666"获面试宝典 来源:https://juejin.cn/post/6844904056230690824 前言 本章的内容主要是想探讨我们在进 ...

  6. 用好 Spring AOP,天降大锅从容应对!

    以下文章来源方志朋的博客,回复"666"获面试宝典 作者 | 何甜甜在吗 来源 | https://juejin.cn/post/6844904087964614670 最近项目进 ...

  7. 《深入理解Spring Cloud与微服务构建》出版啦!

    作者简介 方志朋,毕业于武汉理工大学,CSDN博客专家,专注于微服务.大数据等领域,乐于分享,爱好开源,活跃于各大开源社区.著有<史上最简单的Spring Cloud教程>,累计访问量超过 ...

  8. Spring实战1:Spring初探

    现在的Java程序员赶上了好时候.在将近20年的历史中,Java的发展历经沉浮.尽管有很多为人诟病的产品,例如applets.EJB.Java Data Object(JDO)和数不清的日志框架,Ja ...

  9. Spring入门 IOC

    JAVA就业套餐课:https://edu.csdn.net/combo/detail/1230 课程说明 一. 技术概览 轻量级的企业应用开发越来越受到广大Java应用开发者的追捧,而Spring框 ...

最新文章

  1. Selenium提取数据之标签对象提取文本内容和属性值
  2. python学习笔记之socket(第七天)
  3. systemd.generator — systemd unit generators
  4. PMP杂谈--PMP中一些easy忽视的地方
  5. 学用MVC4做网站五:5.2我的文章
  6. 安装TensorFlow-gpu
  7. 第一次java实验报告
  8. python 网络设备巡检_python写的一个服务器自动巡检工具
  9. 【枚举排列】生成1~n的排列生成可重集的排列
  10. 看Spring实战有感(一)
  11. java劳务派遣信息管理系统servlet员工社保工资信息jsp源码mysql
  12. thrift开源项目研究
  13. python除法运算定律_小数乘法和小数除法知识点整理(转)
  14. python群发邮件 不进垃圾箱_邮件群发如何不进垃圾箱
  15. 版权微talk | 两部门发文,拟出台相关方案,全面加强知识产权保护
  16. wpf之Binging类
  17. 蓝牙芯片设计看上海,终端产品找深圳
  18. h5自动播放视频且有声音的办法
  19. 零售巨头家乐福在西班牙推出家禽区块链食物追踪平台
  20. C/S架构的简单文件传输系统的实现

热门文章

  1. 互联网快讯:花呗接入征信;淘宝上线“秋收节”;猿辅导、掌门教育转型素质教育
  2. JAVA中 怎么判断输入的字符串是不是一个数字
  3. vue外卖二十:商家详情-评价列表:评价列表接口模拟-vuex获取完整数据链流程、滑动better-scroll
  4. java中创建线程的四种方式及线程池详解
  5. 修仙之路(进度68%)
  6. 蓝牙图标突然消失不见!针对我的戴尔笔记本有效
  7. 计算机系统结构WINDLX实验——实验一
  8. xp计算机上的共享文档说是拒绝访问,共享文件夹 拒绝访问
  9. ubuntu16.04在qemu上安装win7虚拟机
  10. Oceanbase查询改写:查询下推