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
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.16</version>
</dependency>
<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 {
@Override
public void getUser() {
System.out.println("默认调用UserDao接口!");
}
}
public interface UserService {
void getUser();
}
public class UserServiceImpl implements UserService {
UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
此时,用户实际是调用了Service层的业务,但当用户需求有变动时,例如此时用户并不想要调用默认的UserDao接口,而是想要调用特殊的接口去链接Oracle数据库,解决方法可以是重新写一个UserDao的实现类,继承接口后重写方法。但是在代码量十分庞大的情况下,这种措施无疑是最坏的做法。
假设此时有一个UserDao实现类:
public class UserDaoOracleImpl implements UserDao {
@Override
public void getUser() {
System.out.println("Oracle调用UserDao接口!");
}
}
而这次,并不直接创建一个新的业务实现类,而是在原有的业务实现类进行一些修改:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public 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/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<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) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello);
}
}
思考:
这个过程就叫控制反转 :
-
控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用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">
<property name="name" value="Ayin"/>
<property name="address" ref="address"/>
<property name="book" >
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>三国演义</value>
</array>
</property>
<property name="hobbys">
<list>
<value>唱</value>
<value>跳</value>
</list>
</property>
<property name="card">
<map>
<entry key="身份证" value="11337383846193885"/>
</map>
</property>
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
</set>
</property>
<property name="wife">
<null></null>
</property>
<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/beans
https://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/beans
https://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/beans
https://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/beans
https://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/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">
<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 {
@Autowired
private Cat cat;
@Autowired
private 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/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.atatyin.pojo"/>
<context:annotation-config/>
</beans>
@Component
@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("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
@ComponentScan("com.atayin.pojo")
@import(UserConfig2.class)
public class UserConfig {
@Bean
public User getUser() {
return new User();
}
}
测试类:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
User user = context.getBean("getUser", User.class);
System.out.println(user.getName());
}
}
这种纯Java的配置方式,在SpringBoot中随处可见!
8、代理模式
为什么要学习代理模式?
代理模式分类:
8.1 静态代理
静态代理,可以简单理解为找一个对象去帮你完成某一件事情。例如:此时小明需要租房,那他可以先找到租房中介,让中介去找房东,并将房子租给小明,此时小明并没有直接和房东进行交接,而是和中介进行交接。
public interface Rent {
public void rent();
}
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东需要出租房子!");
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Proxy implements Rent {
private Host host;
@Override
public 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;
}
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 {
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所需要的包!
<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 {
@Override
public 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"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://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>
<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/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://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包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<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
<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 configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<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&useUnicode=true&characterEncoding=UTF-8&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 {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
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 mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atayin.dao.UserMapper">
<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:
<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&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
2、SqlSessionFactory:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/atayin/dao/*.xml"/>
</bean>
3、SqlSession:
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替代码中已经在使用的 SqlSession 。 SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。
当调用 SQL 方法时(包括由 getMapper() 方法返回的映射器中的方法),SqlSessionTemplate 将会保证使用的 SqlSession 与当前 Spring 的事务相关。 此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。另外,它也负责将 MyBatis 的异常翻译成 Spring 中的 DataAccessExceptions 。
由于模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
可以使用 SqlSessionFactory 作为构造方法(因为它并没有set方法,所以无法用set注入)的参数来创建 SqlSessionTemplate 对象。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
4、增加接口实现类:
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public 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 {
@Test
public 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);
}
}
}
如何理解以上步骤?
10.3 SqlSessionDaoSupport
SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession 。调用 getSqlSession() 方法会得到一个 SqlSessionTemplate ,之后可以用于执行 SQL 方法,就像下面这样:
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> select() {
return getSqlSession().getMapper(UserMapper.class).select();
}
}
在这个类里面,通常更倾向于使用 MapperFactoryBean ,因为它不需要额外的代码。但是,如果需要在 DAO 中做其它非 MyBatis 的工作或需要一个非抽象的实现类,那么这个类就很有用了。
继承该类主要是省去了将SqlSession设置为属性,并为其注入值的过程,也就是说,在之前所述的步骤中,使用SqlSessionTemplate来创建SqlSession可以进行省去。
SqlSessionDaoSupport 需要通过属性设置一个 sqlSessionFactory 或 SqlSessionTemplate 。如果两个属性都被设置了,那么 SqlSessionFactory 将被忽略。
假设类 UserMapperImpl 是 SqlSessionDaoSupport 的子类,可以编写如下的 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>
<delete id="deleteUser" parameterType="int">
deletes from mybatis.user where id = #{id};
</delete>
在UserMapper的实现类中,创建对应的实现方法:
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
@Override
public 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();
}
@Override
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
@Override
public 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>
配置事务的通知,也就是绑定需要配置事务的方法:
<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/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://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/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://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:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
|