Spring是什么?
Spring是一个开源框架,它由Rod Johnson创建,于2003年发布。Spring框架的主要目标是简化Java企业级应用的开发,通过提供一组全面的解决方案,如依赖注入、控制反转(IOC)、面向切面编程(AOP)、事务管理等,使得开发者能够更加专注于业务逻辑的实现,而不是底层的细节。
官网:https://spring.io/projects/spring-framework#learn
GitHub地址:https://github.com/spring-projects/spring-framework
maven依赖:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.2.3</version>
</dependency>
Spring组成
Spring由多个模块组成,主要包括以下几部分:
- Spring Core: 核心容器,提供依赖注入(DI)和面向切面编程(AOP)等功能。
- Spring AOP: 面向切面编程,提供面向切面编程的支持。
- Spring ORM: 对象关系映射,提供对持久层框架的支持,如Hibernate、JPA等。
- Spring DAO: 数据访问对象,提供对数据库访问的支持。
- Spring Web: 提供对Web应用开发的支持,包括Spring MVC等。
- Spring Context: 提供对上下文的支持,包括Bean的创建、配置和管理等。
- Spring Web MVC: Spring MVC框架,用于构建Web应用。
IOC理论推导
正常我们创建项目需要完成如下步骤:
1、创建dao类, 假设我们这里有两种获取用户的实现方式,一种是从mysql中获取,另一种是从oracle中获取。
package com.myLearning.dao;
public interface UserDao {
public void getUser();
}
package com.myLearning.dao;
public class UserDaoMysqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("mysql 获取用户");
}
}
package com.myLearning.dao;
public class UserDaoOracleImpl implements UserDao{
@Override
public void getUser() {
System.out.println("oracle 获取用户");
}
}
2、创建service类, 内部需要创建Dao对象,用于调用dao中的方法。
package com.myLearning.service;
public interface UserService {
public void getUser();
}
package com.myLearning.service;
import com.myLearning.dao.UserDao;
import com.myLearning.dao.UserDaoMysqlImpl;
public class UserServiceImpl implements UserService {
UserDao userDao = new UserDaoMysqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
3、用户层调用service层实现需求
import com.myLearning.service.UserService;
import com.myLearning.service.UserServiceImpl;
public class MyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.getUser();
}
}
以上的代码实现虽然满足了需求,但是如果我们现在需要将mysql改为oracle,那么就需要修改service层,即我们需要修改业务层的代码,这显然是不合理的。
所以我们可以修改UserServiceImpl类,将UserDao对象改为接口类型,然后通过set方法进行注入。
package com.myLearning.service;
import com.myLearning.dao.UserDao;
public class UserServiceImpl implements UserService {
private UserDao userDao;
// set方法实现UserDao的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
然后在我们需求变化的时候,我们只需要修改客户端代码,即MyTest类,将UserDaoMysqlImpl改为UserDaoOracleImpl即可。
import com.myLearning.dao.UserDaoOracleImpl;
import com.myLearning.service.UserService;
import com.myLearning.service.UserServiceImpl;
public class MyTest {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(new UserDaoOracleImpl());
userService.getUser();
}
}
这样我们就可以在不修改业务层代码的情况下,实现数据库的更换,这就是IOC控制反转的思想。
IOC本质
控制反转是一种思想,就是将对象的创建和对象之间的调用过程交给Spring进行管理,而不是由程序员手动管理,所谓控制反转,就是把创建对象的权利交给Spring,而不是由程序员手动创建对象,即获得对象的方式反转了。
在Spring中,IOC的实现是通过依赖注入(Dependency Injection)来实现的,依赖注入就是将对象之间的依赖关系通过配置文件或者注解的方式,由Spring容器来管理,从而实现对象之间的解耦。
使用Spring进行对象的创建
我们继续修改刚刚的代码,我们现在要使用Spring来创建对象,首先我们需要在pom.xml中添加Spring的依赖
然后我们需要创建一个beans.xml的配置文件,在配置文件中配置我们的对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 在这个配置文件中配置对象,相当于我们在java程序中new一个对象 -->
<!-- 这里的id相当于我们new对象时的变量名,class相当于我们准备new的对象类-->
<bean id="mysqlImpl" class="com.myLearning.dao.UserDaoMysqlImpl"/>
<bean id="oracleImpl" class="com.myLearning.dao.UserDaoOracleImpl"/>
<bean id="userServiceImpl" class="com.myLearning.service.UserServiceImpl">
<!-- 这里property内部,name表示属性名,ref引用bean中创建的对象,value表示实际的值-->
<property name="userDao" ref="mysqlImpl"/>
</bean>
</beans>
然后我们就可以直接在Test中获取Spring容器中为我们创建的对象了,而不是自己new对象了
import com.myLearning.dao.UserDaoOracleImpl;
import com.myLearning.service.UserService;
import com.myLearning.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userServiceImpl");
userService.getUser();
}
}
我们现在只需要修改xml配置文件,就可以切换不同的dao实现类了,而不需要修改程序代码,这就是Spring的IOC思想,即对象交由Spring来创建、管理以及装配。
IOC创建对象的方式
我们先创建一个User类:
package com.myLearning;
public class User {
private String name;
public User(){
System.out.println("User 对象被无参构造方式创建了!");
}
public User(String name){
this.name = name;
System.out.println("User 对象被有参构造方式创建了!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后再beans.xml中配置:
<bean id="user" class="com.myLearning.User">
<property name="name" value="张三"/>
</bean>
在默认情况下,Spring会调用类的无参构造方法来创建对象,如果需要调用有参构造方法,一共有三种方法:
1、通过index设置参数位置
<bean id="user" class="com.myLearning.User">
<constructor-arg index="0" value="张三"/>
</bean>
2、通过type设置参数类型
<bean id="user" class="com.myLearning.User">
<constructor-arg type="java.lang.String" value="张三"/>
</bean>
3、通过name设置参数名称
<bean id="user" class="com.myLearning.User">
<constructor-arg name="name" value="张三"/>
</bean>
Spring配置
bean标签
1、id属性:用于表示对象,相当于变量名,唯一标识符,不能包含特殊字符
2、class属性:用于指示创建的对象类名,填类的全限定名
3、name属性:用于创建该对象的别名,可以包含特殊字符,可以设置多个,多个之间用逗号分隔
<bean id="user" class="com.myLearning.User" name="user1,user2"/>
alias标签
用于设置别名,可以设置多个,多个之间用逗号分隔
<alias name="user" alias="user1"/>
<alias name="user" alias="user2"/>
import标签
用于导入其他配置文件,可以设置多个,多个之间用逗号分隔
<import resource="beans.xml"/>
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
依赖注入DI
依赖注入(Dependency Injection,DI)是一种设计模式,用于实现控制反转(Inversion of Control,IoC),目的是减少代码间的耦合。
依赖指的是bean对象的创建依赖于容器
注入指的是bean对象中的所有属性,由容器来注入
测试环境搭建
创建实体类
package com.myLearning.pojo;
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
package com.myLearning.pojo;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String[] getBooks() {
return books;
}
public void setBooks(String[] books) {
this.books = books;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public Map<String, String> getCard() {
return card;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public Set<String> getGames() {
return games;
}
public void setGames(Set<String> games) {
this.games = games;
}
public String getWife() {
return wife;
}
public void setWife(String wife) {
this.wife = wife;
}
public Properties getInfo() {
return info;
}
public void setInfo(Properties info) {
this.info = info;
}
}
构造器注入
1、通过index设置参数索引
<bean id="user" class="com.myLearning.User">
<constructor-arg index="0" value="张三"/>
</bean>
2、通过type设置参数类型
<bean id="user" class="com.myLearning.User">
<constructor-arg type="java.lang.String" value="张三"/>
</bean>
3、通过name设置参数名称
<bean id="user" class="com.myLearning.User">
<constructor-arg name="name" value="张三"/>
</bean>
set注入
<bean id="address" class="com.myLearning.pojo.Address">
<property name="address" value="鸠拉·特恩佩斯特联邦国"/>
</bean>
<bean id="student" class="com.myLearning.pojo.Student">
<!-- 普通值注入 -->
<property name="name" value="利姆露"/>
<!-- 使用bean内对象的引用注入-->
<property name="address" ref="address"/>
<!-- 数组-->
<property name="books">
<array>
<value>突然穿越了怎么办</value>
<value>帝王心术</value>
<value>如何偷偷去酒馆不被发现</value>
</array>
</property>
<!-- List-->
<property name="hobbies">
<list>
<value>吃好吃的</value>
<value>看好看的</value>
<value>玩好玩的</value>
</list>
</property>
<!-- Map-->
<property name="card">
<map>
<entry key="id-card" value="10577964"/>
<entry key="酒馆会员卡" value="100066"/>
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>竞技场</value>
<value>举办宴会</value>
</set>
</property>
<!-- 设置null值-->
<property name="wife">
<null/>
</property>
<!-- Properties-->
<property name="info">
<props>
<prop key="种族">史莱姆</prop>
</props>
</property>
</bean>
</beans>
p注入
使用p命名空间注入属性值需要先导入p命名空间
xmlns:p="http://www.springframework.org/schema/p"
<bean id="address2" class="com.myLearning.pojo.Address"
p:address="朱拉坦派斯特联邦国"/>
使用p命名空间注入可以简化xml配置,相当于之前配置的property
c注入
使用c命名空间注入属性值需要先导入c命名空间
xmlns:c="http://www.springframework.org/schema/c"
同时使用c注入需要先在类中定义有参构造器
<bean id="user" class="com.myLearning.pojo.User"
c:name="张三" c:age="18"/>
使用c命名空间注入可以简化xml配置,相当于之前配置的constructor-arg
bean作用域
bean的作用域包含以下几种
- singleton:单例(默认):IOC容器中只有一个bean的实例
- prototype:原型(多例):IOC容器中每次获取bean的时候都会创建一个新的bean
- request:在web项目中,设置bean的作用域为request表示该bean在一个请求范围内有效
- session:在web项目中,设置bean的作用域为session表示该bean在一个会话范围内有效
<bean id="user" class="com.myLearning.pojo.User" scope="singleton"/>
bean自动装配
假设我们有一个人(类),这个人有一只狗
package com.myLearning.pojo;
public class Dog {
public void wangwang(){
System.out.println("汪汪");
}
}
package com.myLearning.pojo;
public class Person {
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
private Dog dog;
}
然后我们来编写xml配置文件
一般我们需要手动注入创建对象的属性
<bean id="dog" class="com.myLearning.pojo.Dog"/>
<bean id="person" class="com.myLearning.pojo.Person">
<property name="dog" ref="dog"/>
</bean>
但是我们可以设置autowire属性,让spring自动装配,这个属性可以设置为byName和byType,byName表示根据名字自动装配,byType表示根据类型自动装配
根据名字id自动装配,这里搜索的是person类中的dog属性,所以我们需要将小狗类对象的id设置为dog才能被搜索到
<bean id="dog" class="com.myLearning.pojo.Dog"/>
<bean id="person" class="com.myLearning.pojo.Person" autowire="byName"/>
根据类型自动装配
<bean id="dog" class="com.myLearning.pojo.Dog"/>
<bean id="person" class="com.myLearning.pojo.Person" autowire="byType"/>
使用注解完成自动装配
使用注解完成自动装配需要先开启注解支持,至少保证配置文件包含如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
使用@Autowired注解完成自动装配,这个注解可以放在属性上,也可以放在set方法上,也可以放在构造方法上
public class Person {
@Autowired
private Dog dog;
public void shout() {
dog.shout();
}
}
@Autowired注解默认是先按照类型装配的,如果没有唯一的对应类型,则会试图查找属性名对应的bean, 但如果仍然找不到对应属性名的对象,则会报错,此时需要使用@Qualifier注解指定bean的id
假设我们的配置文件中有如下bean
<bean id="dog1" class="com.myLearning.pojo.Dog"/>
<bean id="dog2" class="com.myLearning.pojo.Dog"/>
那么在Person类中,如果使用@Autowired注解,则无法找到唯一的dog对象,此时需要使用@Qualifier注解,它可以指定bean的id
public class Person {
@Autowired
@Qualifier("dog1")
private Dog dog;
public void shout() {
dog.shout();
}
}
也通过@Ressource注解完成自动装配,这个注解和@Autowired注解类似,使用时放在属性上
使用前需要添加maven依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
它会先按照名称进行装配,如果找不到对应名称的bean,则会按照类型进行装配,如果仍然找不到,则会报错,可以通过(name=“dog1”)指定bean的id
// public class Person {
// @Resource
// private Dog dog;
// public void shout() {
// dog.shout();
// }
// }
public class Person {
@Resource(name="dog1")
private Dog dog;
public void shout() {
dog.shout();
}
}
使用注解开发
使用注解需要有注解的支持:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
我们创建一个applicatonContext.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 指定扫描的包,这个包下的注解会生效 -->
<context:component-scan base-package="com.myLearning.pojo"/>
<context:annotation-config/>
</beans>
@Component注解与@Value注解
@Component注解: 将类标识为Spring容器中的一个Bean,Spring会自动扫描并管理这些Bean。相当于在XML配置文件中定义了一个Bean。
@Value注解: 用于为Bean的属性注入值,可以用于字段、方法参数、构造函数参数等。相当于在XML配置文件中为Bean的属性指定了一个值。
package com.myLearning.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value("利姆露")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
根据mvc三层架构分层,@Component注解在dao、service、controller层有几个作用相同的注解:
- @Controller: 用于标识一个控制器类,通常用于处理HTTP请求。
- @Service: 用于标识一个服务类,通常用于处理业务逻辑。
- @Repository: 用于标识一个数据访问对象类,通常用于访问数据库。
这几个注解的作用和@Component一样,只是名字不同
@Autowired注解 与 @Qualifier注解
用于自动装配,参考前文介绍
@Scope注解
用于指定对应Bean的作用域,有singleton、prototype、request、session、globalSession等选项。
@Component
@Scope("singleton")
public class User {
@Value("利姆露")
private String name;
}
使用纯Java的方式来配置Spring
我们也可以完全不使用XML配置文件,而是使用纯Java的方式来配置Spring。具体步骤如下:
- 创建一个配置类,使用@Configuration注解标识该类为一个配置类,这个注解相当于我们之前xml配置文件中的标签,其中可以包含多个bean.
- 在配置类中使用@Bean注解来定义一个Bean,这个注解相当于我们之前xml配置文件中的标签。
// 配置类
package com.myLearning;
import com.myLearning.controller.UserController;
import com.myLearning.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 这个注解同时也会将这个类注册到Spring容器中,因为它本身也是一个@Component
@ComponentScan("com.myLearning") //这个注解相当于我们之前xml配置文件中的<context:component-scan>标签,它会扫描指定包下的所有类,并将它们注册到Spring容器中
public class AppConfig {
@Bean
public User getUser() {
return new User();
}
@Bean
public UserController getUserController() {
return new UserController();
}
}
// User类
package com.myLearning.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value("利姆露")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 在需要使用该Bean的地方,使用@Autowired注解来自动装配该Bean。
package com.myLearning.controller;
import com.myLearning.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private User user;
public void printUser() {
System.out.println(user.getName());
}
}
在我们使用的时候,我们需要使用AnnotationConfigApplicationContext来加载配置文件,然后获取到UserController的实例
import com.myLearning.AppConfig;
import com.myLearning.controller.UserController;
import com.myLearning.pojo.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//这里既可以使用刚刚注册的getUserController方法用于获取实例,也可以直接使用UserController的类名获取实例
// UserController userController = context.getBean(UserController.class);
UserController userController = (UserController) context.getBean("getUserController");
userController.printUser();
}
}
如果我们想将其他配置类导入到一个配置类中,我们可以使用@Import注解,例如:
@Configuration
@Import({AppConfig1.class, AppConfig2.class})
public class AppConfig {
@Bean
public UserController getUserController() {
return new UserController();
}
}
代理模式
代理模式是一种设计模式,它允许我们通过代理对象来访问目标对象,而不需要直接访问目标对象。在Spring中,代理模式被广泛使用,例如在AOP(面向切面编程)中,代理对象用于在目标方法执行前后添加额外的逻辑。
代理模式的分类
- 静态代理
- 动态代理
静态代理
代理模式通常包含三个角色:
- 抽象主题(Subject):定义了代理类和真实主题的公共接口,这样代理类就可以通过实现这个接口来代理真实主题。
- 真实主题(Real Subject):实现了抽象主题定义的接口,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理类(Proxy):代理类包含了真实主题的引用,并在调用真实主题的方法前后可以添加一些额外的操作。
假设我们有一个房东要卖房,它们不想直接和购房者打交道,于是请了一个中介来代理它们。房东是真实主题,中介是代理类,购房者是客户端。
我们先定义一个接口,表示房东和中介的公共行为:
package com.myLearning.sellHouse;
public interface Sell {
void sellHouse();
}
然后我们定义实现这个接口的房东
package com.myLearning.sellHouse;
public class Host implements Sell{
public void sellHouse() {
System.out.println("房屋主人卖出房子!");
}
}
但是我们通常不会直接找到房东买房,而是通过中介代理,所以我们定义一个代理类,它不仅负责帮助房东卖房子,还帮助我们签订合同
package com.myLearning.sellHouse;
public class Proxy implements Sell{
private Host host;
public Proxy(Host host) {
this.host = host;
}
private void makeContract(){
System.out.println("中介协商签合同");
}
public void sellHouse() {
makeContract();
host.sellHouse();
}
}
然后我们在客户端就可以使用代理类来买房子和签合同啦
package com.myLearning.sellHouse;
public class Client {
public static void main(String[] args) {
Host host = new Host();
// 房主交给中介卖房
Proxy proxy = new Proxy(host);
// 通过中介签合同、买房
proxy.sellHouse();
}
}
代理模式的优点
- 代理模式能将代理对象与真实对象分离,在一定程度上降低了系统的耦合度。
- 代理模式能将一些与真实对象无关的操作,如统计、访问控制等,集中到代理类中完成,这样既可减少真实对象的复杂性,也易维护和扩展。
- 代理模式能将真实对象的使用者与真实对象解耦,在一定程度上降低了使用者的复杂性。
动态代理
动态代理是代理模式的一种实现方式,它可以在运行时动态地创建代理类,而不需要提前编写代理类的代码。动态代理通常使用Java的反射机制来实现。
动态代理的原理
动态代理的原理是通过Java的反射机制,在运行时动态地创建代理类,并使用代理类来调用真实对象的方法。具体来说,动态代理的实现步骤如下:
- 创建一个实现了InvocationHandler接口的处理器类,该处理器类负责处理代理对象的方法调用。在处理器类中,需要重写invoke方法,该方法会在代理对象的方法被调用时被调用。
- 创建一个实现了InvocationHandler接口的处理器对象,并将真实对象作为参数传递给处理器对象的构造方法。
- 使用Proxy类的newProxyInstance方法创建代理对象。该方法需要传入三个参数:ClassLoader类加载器,代理对象实现的接口数组,处理器对象。该方法会返回一个代理对象,该代理对象会使用处理器对象来处理方法调用。
- 使用代理对象调用真实对象的方法。
动态代理的示例
下面是一个动态代理的示例,该示例使用动态代理来实现一个卖房子的功能。
package com.myLearning.sellHouse;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("中介开始卖房");
Object result = method.invoke(target, args);
System.out.println("中介卖房结束");
return result;
}
public static void main(String[] args) {
Host host = new Host();
// 创建代理对象
DynamicProxy dynamicProxy = new DynamicProxy(host);
ClassLoader classLoader = host.getClass().getClassLoader(); // 获取目标对象的类加载器
Class<?>[] interfaces = host.getClass().getInterfaces(); // 获取目标对象实现的接口
Sell sellHouseProxy = (Sell) Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);
// 使用代理对象调用真实对象的方法
sellHouseProxy.sellHouse();
}
}
动态代理的优点
- 动态代理可以在运行时创建代理对象,不需要在编译时生成代理类。
- 动态代理可以代理任何实现了接口的类,不需要知道具体的实现细节。
- 动态代理可以灵活地添加额外的功能,例如日志记录、权限控制等。
Spring AOP
AOP(面向切面编程)是Spring框架中的一个重要特性,它允许我们在不修改原有代码的情况下,通过定义切面(Aspect)来增强或修改原有的功能。
切面(Aspect)
切面是AOP中的一个重要概念,它定义了在哪些地方(Join Points)执行哪些操作(Advice)。切面通常由一个类和一个或多个方法组成,这些方法定义了在特定条件下要执行的操作。
连接点(Join Points)
连接点是程序执行过程中的一个特定点,例如方法调用、异常抛出等。切面可以在这些连接点处执行特定的操作。
通知(Advice)
通知是切面中的一个方法,它定义了在连接点处要执行的操作。通知可以是前置通知(Before Advice)、后置通知(After Advice)、环绕通知(Around Advice)等。
切入点(Pointcut)
切入点是一个表达式,用于定义在哪些连接点处执行通知。切入点可以基于方法名、参数、注解等条件进行匹配。
Spring AOP的使用
万事之前,使用aop我们也需要导入aop相关maven依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
方式一
首先我们先编写我们的业务类
package com.myLearning.service;
public interface UserService {
void add();
void update();
void delete();
void select();
}
package com.myLearning.service;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("执行了添加用户的方法");
}
@Override
public void update() {
System.out.println("执行了更新用户的方法");
}
@Override
public void delete() {
System.out.println("执行了删除用户的方法");
}
@Override
public void select() {
System.out.println("执行了查找用户的方法");
}
public UserServiceImpl() {
}
}
然后现在我们希望在执行这些方法的前后添加记录日志的代码,但是我们又不希望改动我们已经编写好的业务代码,这个时候我们就可以使用Spring AOP来实现这个功能。
我们先编写我们的Log代码,需要分别实现MethodBeforeAdvice 接口以及AfterReturningAdvice 接口,分别用于在业务方法的前后添加日志记录。
package com.myLearning.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before Method: " + method.getName());
}
}
package com.myLearning.log;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("ReturnedValue: " + returnValue + " Method: " + method + " Args: " + args);
}
}
现在我们已经编写好了准备添加到业务前后的Log类,现在我们需要将这两个类通过Spring AOP添加到我们的业务类中,首先我们需要编写一个配置文件applicationContext.xml,也即是我们之前用于配置bean的配置文件,我们需要通过这个配置文件来配置我们的AOP。
类似于我们之前配置的自动装配需要添加配置约束,我们的aop也需要添加配置约束,在xml文件中添加约束,类似以下形式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
现在我们在配置文件中添加我们的Log类,业务类,以及配置aop:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 指定扫描的包,这个包下的注解会生效 -->
<!-- <context:component-scan base-package="com.myLearning.pojo"/>-->
<context:annotation-config/>
<bean id="userService" class="com.myLearning.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.myLearning.log.BeforeLog"/>
<bean id="afterLog" class="com.myLearning.log.AfterLog"/>
<!-- 配置aop,需要导入aop约束-->
<!-- 使用Spring API接口配置-->
<aop:config>
<!-- 切入点 expression用于指定要切入的位置(方法)固定表达式为 execution(返回值 类 方法 参数)-->
<aop:pointcut id="pointcut" expression="execution(* com.myLearning.service.UserServiceImpl.*(..))"/>
<!-- 编写环绕通知,指明要加入的通知类,以及要切入的位置-->
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
简单测试一下,发现我们已经在不改动业务代码的情况下,在方法执行前后加入了日志!
import com.myLearning.AppConfig;
import com.myLearning.controller.UserController;
import com.myLearning.pojo.User;
import com.myLearning.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
方式二
以上是通过设置通知来配置aop设置的,我们还可以通过直接编写切面类(切面类用于完成如日志的模块功能的类,通知相当于完成功能的方法,我们前面的设置是直接设置通知advisor的方式完成的)来完成aop的配置:
我们先简单编写一个切面类:
package com.myLearning.log;
public class DiyPointCut {
public void before(){
System.out.println("before method");
}
public void after(){
System.out.println("after method");
}
}
然后将这个切面类配置到spring的配置文件中:
<bean id="userService" class="com.myLearning.service.UserServiceImpl"/>
<bean id="diyPointCut" class="com.myLearning.log.DiyPointCut"/>
<aop:config>
<!-- 切入点 expression用于指定要切入的位置(方法)固定表达式为 execution(返回值 类 方法 参数)-->
<aop:pointcut id="pointcut" expression="execution(* com.myLearning.service.UserServiceImpl.*(..))"/>
<!-- 自定义切面 -->
<aop:aspect ref="diyPointCut">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
通过这样的方法,我们可以达到与前一种方法同样的效果
方式三
除了以上两种方法,我们还可以通过注解来直接设置切面
首先我们需要写一个切面类,并且在编写时添加注解
package com.myLearning.log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
// 标识这是一个切面
@Aspect
public class AnnotationPointCut {
// 标识切入点
@Before("execution(* com.myLearning.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("Before Method");
}
// 标识切入点
@After("execution(* com.myLearning.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("After Method");
}
// 表示环绕切入点
@Around("execution(* com.myLearning.service.UserServiceImpl.*(..))")
// 参数是连接点,用于执行业务实际方法,以及可以显示执行的业务方法的一些信息
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
// 执行实际业务代码
Object ret = joinPoint.proceed();
System.out.println("环绕后");
return ret;
}
}
然后我们需要再Spring配置文件中配置这个类,并且开启注解支持
<bean id="userService" class="com.myLearning.service.UserServiceImpl"/>
<bean id="annotationPointCut" class="com.myLearning.log.AnnotationPointCut"/>
<!-- 开启注解支持-->
<aop:aspectj-autoproxy/>
整合Spring和Mybatis
万事之前,先导包
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.4</version>
</dependency>
配置基础Mybatis环境
创建实体类,相对于的数据库可能需要自己参考实体类创建一下,或者可以参考我前一篇博客的数据库创建
package com.myLearning.pojo;
import org.apache.ibatis.type.Alias;
import java.io.Serializable;
@Alias("user")
public class User implements Serializable {
private int id;
private String name;
private String pwd;
public User() {
}
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
创建Mapper接口
package com.myLearning.mapper;
import com.myLearning.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> selectAllUser();
}
创建Mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--用于绑定刚刚创建的Dao(Mapper)接口-->
<mapper namespace="com.myLearning.mapper.UserMapper">
<select id="selectAllUser" resultType="com.myLearning.pojo.User">
select * from user
</select>
</mapper>
创建mybatis-config.xml配置文件, 注意,我这里并没有配置数据源以及mapper映射,这是因为我们一会后会将Spring与mybatis整合,我们会在Spring的配置文件中配置数据源之类的东西
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.myLearning.pojo"/>
</typeAliases>
<!-- <settings>-->
<!-- <setting name="" value=""/>-->
<!-- </settings>-->
</configuration>
设置Spring的配置
在我们创建好了mybatis基础环境后,我们现在可以来编写Spring的配置文件了,在这个文件中,我们主要需要配置
数据源类org.springframework.jdbc.datasource.DriverManagerDataSource,这个就是我们之前在Mybatis中配置的数据源,我们现在在Spring中配置。
工厂类org.mybatis.spring.SqlSessionFactoryBean, 这个是我们之前在Mybatis工具类中创建的工厂类,现在我们将其配置到Spring中。
SqlSession类 org.mybatis.spring.SqlSessionTemplate,这个就是我们之前在Mybatis工具类中创建的SqlSession类,主要用于执行我们的sql语句的,现在我们将其配置到Spring中。
这几个类
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 指定扫描的包,这个包下的注解会生效 -->
<!-- <context:component-scan base-package="com.myLearning.pojo"/>-->
<context:annotation-config/>
<!--首先我们需要配置一个数据源,我们现在使用Spring的数据源代替mybatis的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--我们还需要一个sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/myLearning/mapper/*.xml"/>
</bean>
<!-- 获得sqlSession对象-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>
现在我们有了sqlSession后,我们就可以创建一个UserMapper的实现类UserMapperImpl,用来通过sqlSession对象来执行sql语句了。
package com.myLearning.mapper;
import com.myLearning.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper {
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> selectAllUser() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
return userMapper.selectAllUser();
}
}
同样,我们把它注册到Spring中
<!-- 获得UserMapper对象-->
<bean id="userMapper" class="com.myLearning.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
当然,我们可能需要一个applicationContext.xml来管理所有的Spring配置,在其中导入我们刚刚创建的Spring-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<import resource="spring-dao.xml"/>
</beans>
简单测试一下
package com.myLearning.dao;
import com.myLearning.mapper.UserMapper;
import com.myLearning.pojo.User;
import com.myLearning.util.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class UserMapperTest {
@Test
public void selectUserListTest(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = (UserMapper)context.getBean("userMapper");
List<User> users = userMapper.selectAllUser();
for (User user : users) {
System.out.println(user);
}
}
}
当然,我们还可以有另一种方式来编写UserMapperImpl类,即让它继承SqlSessionDaoSupport类,这样我们就可以直接使用getSqlSession()方法来获取SqlSession对象,而不需要自己创建SqlSession对象。
package com.myLearning.mapper;
import com.myLearning.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> selectAllUser() {
UserMapper userMapper = getSqlSession().getMapper(UserMapper.class);
return userMapper.selectAllUser();
}
}
但要注意,此时我们注册的时候,需要注入的不再是SqlSessionTemplate,而是SqlSessionFactory。
<!-- 获得UserMapper对象-->
<bean id="userMapper" class="com.myLearning.mapper.UserMapperImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
使用Spring配置声明式事务
首先要确保Spring的配置文件有事务的声明空间,即引入tx命名空间,类似如下内容,主要是包含tx相关的:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
首先我们需要再Spring配置文件中配置事务管理器
<!-- 配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
然后我们使用aop的通知来实现事务,这里Spring已经为我们写好了事务类型的通知,我们只需要简单配置即可
<!-- 配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 给哪些方法配置事务-->
<tx:attributes>
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
然后我们需要使用aop配置将通知和切入点结合起来
<!-- aop配置事务切入-->
<aop:config>
<!-- 设置切入点,给com.myLearning.mapper包内所有类中的所有方法配置事务-->
<aop:pointcut id="txPointCut" expression="execution(* com.myLearning.mapper.*.*(..))"/>
<!-- 设置通知-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
以此,我们就为我们的com.myLearning.mapper包内所有类中的所有方法配置了事务管理了
笔记总结于视频:https://www.bilibili.com/video/BV1WE411d7Dv?vd_source=16bf0c507e4a78c3ca31a05dff1bee4e&spm_id_from=333.788.videopod.episodes