Spring Framework
一、IOC/DI
IoC(Inversion of Control)控制反转
DI(dependency injection)依赖注入
IoC/DI指的是一个过程:对象的创建仅仅通过Spring容器负责,Spring容器可以通过对象的构造方法或工厂方法进行实例化对象。在创建对象过程中,如果对象需要依赖其他对象,也可以直接在Spring容器中注入到当前对象。
整个过程中对象本身在容器中控制自己的实例化(所以叫做控制反转),通过构造方法或setter方法把依赖对象注入到属性中(所以又叫做依赖注入)。
二、Bean创建的两种方式
1. BeanFactory
通过读取、解析配置文件中Bean的配置信息,容器通过反射使用构造方法实例化对象,默认使用无参构造。对象由容器进行管理。
配置bean:
<bean id="emp1" class="com.gsy.pojo.Emp"> </bean>
2. FactoryBean
配置自定义对象工厂的信息,容器从工厂获取创建的对象,对象由容器进行管理。2.1 静态工厂
例如:
people工厂类:
public class PeopleStaticFactory { private static People peo = new People(); public static People newInstance(){ return peo; } }
配置bean:
<bean id="peo3" class="com.gsy.factory.PeopleStaticFactory" factory-method="newInstance"/>
2.2 实例工厂
例如:
people工厂类:
public class PeopleFactory { People peo = new People(); public People getInstance(){ return peo; } }
配置bean:
实例工厂需要先创建工厂的实例才能调用方法。
<!-- 创建工厂实例 --> <bean id="factory" class="com.gsy.factory.PeopleFactory"></bean> <!-- factory-bean:工厂对象的id factory-method:创建当前bean的方法名称 --> <bean id="peo2" factory-bean="factory" factory-method="getInstance"></bean>
三、属性注入
1. 手动注入
手动注入又分为构造注入和设值注入两种方式
1.1 构造注入
构造注入是通过构造方法将属性注入,即在创建对象时就完成属性的注入。
使用构造注入要提供对应参数的构造方法。
需要注意的是,在声明了有参构造方法后,需要显式声明无参构造,否则无参构造方法将被覆盖。
配置bean:
<bean id="peo4" class="com.gsy.pojo.People"> <constructor-arg type="int" value="1"></constructor-arg> <constructor-arg type="java.lang.String" value="张三"></constructor-arg> </bean>
注入引用类型的属性要求该类型的对象已经被spring容器托管,即配置了对应的bean,ref指定对应bean的id即可。参数说明:
constructor-org:
name:参数名
index:参数索引
type:参数类型
以上三个参数不是都要指定,只要能确定参数即可。
ref:引用类型
value:参数值
1.2 设值注入
设值注入通过setter方法实现,所以要先有对象才能进行设值注入。
设值注入一般是配合无参构造一起使用,所有要同时具备无参构造和对应的setter方法。
配置bean:
<bean id="peo5" class="com.gsy.pojo.People"> <property name="id" value="2"></property> <property name="name" value="李四"></property> </bean>
参数说明:
name:属性名
value:属性值
ref:引用类型
ref用法同1.1 构造注入
两种方式可以混合使用(构造注入用于强制依赖的注入,setter注入用于可选依赖的注入)
<bean id="peo6" class="com.gsy.pojo.People"> <constructor-arg index="0" value="3"></constructor-arg> <property name="name" value="王五"></property> </bean>
1.3 其他类型属性的注入
无论是构造注入还是设值注入都提供了value和ref进行设置值。这两个属性只能给属性赋予简单数据类型或其他bean的引用。
1.3.1 Set类型

<bean id="peo7" class="com.gsy.pojo.People"> <property name="hover"> <set> <value>abc</value> <value>efg</value> </set> </property> </bean>
1.3.2 List类型

<bean id="peo7" class="com.bjsxt.pojo.People"> <property name="subjects"> <list> <value>java</value> <value>前端</value> </list> </property> </bean>
1.3.3 Array类型

<bean id="peo7" class="com.bjsxt.pojo.People"> <property name="teachers"> <array> <value>小明</value> <value>小花</value> </array> </property> </bean>
1.3.4 Map类型

<bean id="peo7" class="com.gsy.pojo.People"> <property name="phone"> <map> <entry key="姓名" value="gsy"></entry> </map> </property> </bean>
1.3.5 Null类型

<bean id="peo7" class="com.gsy.pojo.People"> <property name="phone"> <null></null> </property> </bean>
1.3.6 其他引用类型
如果需要引用其他Bean,直接在property标签中使用ref引用就可以。使用子标签ref也可以,但是没有直接用ref属性的方式简单。
2. 自动注入
自动注入是Bean之间的自动注入,使用自动注入的前提是Spring容器中必须有能被自动注入的Bean
在Spring中,允许Bean的自动注入,有两种方式进行配置。
-
-
no:不自动注入。
-
byName:
-
通过名称自动注入。名称:属性名与bean的id属性值。
-
会自动寻找容器中与注入值属性名同名的id属性值的bean进行注入。
-
底层为setter注入,设值注入。
-
-
byType:
-
通过类型自动注入。类型:属性类型与bean的class类型。
-
会自动寻找容器中与注入值类型相同的bean进行注入。如果有多个相同类型的bean注入会出现异常。
-
底层为setter注入,设值注入。
-
-
constructor:
-
通过构造方法进行注入。调用有参构造方法完成对象创建及属性自动注入。
-
寻找bean的有参构造方法中只包含需要注入属性参数的构造方法。自动属性注入。
-
- 先通过属性name找bean,找不到再通过属性的type
-
-
-
default:使用上级标签
<beans>
的default-autowire属性值。 -
-
有6个取值,官方文档如下:
prototype:bean是原型的,每次获取bean都重新实例化。
request:每次请求重新实例化对象,同一个请求中多次获取时单例的。
session:每个会话内bean是单例的。
application:整个应用程序对象内bean是单例的。
单例设计模式。
scope的默认值,表示该bena是单例的,属于单例设计模式中的饿汉式(在类加载时就创建对象且只创建一次)
多线程情况下,公用一个对象,线程不安全。
原形设计模式。
表示该bean是原形的,在获取该对象时才创建且每次获取都会重新创建。
1. 如果bean的scope是单例的,bean不是线程安全的。
/* 核心思想: 1. 构造方法私有 2. 对外提供一个能够获取对象的方法。 饿汉式: 优点:实现简单 缺点:无论是否使用当前类对象,加载类时一定会实例化。 */ public class Singleton { // 之所以叫做饿汉式:因为类加载时就创建了对象 private static Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return singleton; } }
1.2 懒汉式
/** * 核心思想: * 1. 构造方法私有。 * 2. 对外提供一个能够获取对象的方法。 * * 懒汉式优点和缺点: * 优点: * 按需创建对象。不会在加载类时直接实例化对象。 * 缺点: * 写法相对复杂。 * 多线程环境下,第一次实例化对象效率低。 */ public class Singleton2 { //懒汉式:不会立即实例化 private static Singleton2 singleton2; private Singleton2() {} public static Singleton2 getInstance() { if (singleton2 == null) {// 不是第一次访问的线程,直接通过if判断条件不成立。直接return synchronized (Singleton2.class) { if(singleton2==null) {// 防止多个线程已经执行到synchronized singleton2 = new Singleton2(); } } } return singleton2; } }
第一个非空判断放在锁外面可以保证不用每个线程都要先等待获得锁在进行是否非空的判断,这样在该对象不为空时能提高效率;
第二个非空判断用于解决多线程问题,例如Thread1拿到了锁,Thread2执行完外层的非空判断阻塞,Thread1创建了对象,释放锁,此时Thread2获得锁,但没有非空判断,再次创建对象
所以第二个非空判断也是有必要的。
五、循环注入
1. 概念
循环依赖即 多个类相互依赖形成了闭环。
2. 循环依赖的场景
多例(原型模式)setter注入:每次获取对象都会新建,A创建后需要B,创建了B需要注入A,去获得A对象时就会新建,再去获得B也会新建。
构造注入:创建对象时就要完成注入,A的创建需要B,B的创建需要A,无法解决
3. 解决循环依赖
如果两个类都使用设值注入 且 scope为singleton就不会出现问题,因为单例默认有三级缓存(DefaultSingletonBeanRegistry),可以暂时缓存没有被实例化完成的bean。
3.1 单例模式创建对象步骤
Spring的单例对象初始化分为三步:
1. 实例化:调用对象的构造方法创建对象;
2. 注入:填充属性,对bean的依赖属性进行填充;
3. 初始化:属性注入后,执行自定义初始化操作。
3.2 循环依赖问题
例如:B的实例对象属于A的一个属性,而A的实例对象又是B的一个属性,这就造成了循环依赖。
3.3 解决循环依赖问题
- 三级缓存(singletonFactories):存放创建bean的工厂,便于扩展创建代理对象
- 二级缓存(earlySingletonObjects):存放早期暴露出来的对象,属性还没有填充完整。
- 一级缓存(singletonObjects):存放实例化,属性注入,初始化完成的完整对象。
其实根据上面的原理使用二级缓存就可以解决循环依赖问题,spring使用三级缓存是为了方便以后进行扩展。
六、BeanFactory和ApplicationContext
1. BeanFactory接口
BeanFactory接口是spring中的顶级接口,定义了Spring容器最基本的功能,是SpringIoC的最核心接口。
getBean方法其实就是BeanFactory的方法。
BeanFactory最常用实现类是XmlBeanFactory。
FileSystemResource fsr = new FileSystemResource("C:\IdeaWS\spring_day01\src\main\resources\application.xml"); XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(fsr); Object teacher = xmlBeanFactory.getBean("teacher"); System.out.println(teacher);
但是从Spring 3.1 版本开始,使用DefaultListableBeanFactory和XMLBeanDefinitionReader替代了上面写法。
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader xbdr = new XmlBeanDefinitionReader(factory); xbdr.loadBeanDefinitions(new FileSystemResource("C:\IdeaWS\spring_day01\src\main\resources\application.xml")); Object teacher = factory.getBean("teacher");
2. ApplicationContext
ApplicationContext是BeanFactory的子接口,拥有更多的功能,除了有BeanFactory的功能,还包含了:
-
-
国际化(MessageSource)
-
访问资源,如URL和文件(ResourceLoader)
-
消息发送机制(ApplicationEventPublisher)
-
在使用时ApplicationContext时多使用ClassPathXmlApplicationContext
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Object teacher = applicationContext.getBean("teacher");
七、Spring整合MyBatis
在pom.xml中引入依赖:

<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.16</version> </dependency> <!-- DataSource的实现类DriverManageDataSource实现所在的包 --> <!-- 把数据库连接操作交给Spring,后面事务才能使用Spring的声明式事务--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.16</version> </dependency> <!-- Spring整合MyBatis的依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency>
创建spring配置文件:
①spring管理数据源(连接池)

<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/ssm?userUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true"/> <property name="username" value="root"/> <property name="password" value="gsy666"/> </bean>
class="org.springframework.jdbc.datasource.DriverManagerDataSource": spring提供的数据源
class="com.alibaba.druid.pool.DruidDataSource": druid提供的数据源
更换数据源只需要更换class
②spring管理sqlSessionFactory

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="typeAliasesPackage" value="com.gsy.pojo"/> </bean>
sqlSessionFactory是顶级接口,但sqlSessionFactoryBean不是SqlSessionFactory的直接实现类。
sqlSessionFactoryBean提供的getObject()方法可以返回一个SqlSessionFactory的实现类,
然后通过SqlSessionFactory的实现类的openSession()方法可以获得SqlSession。
typeAliasesPackage:扫描整个包中的类定义别名(别名为类名)
③spring管理接口绑定

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--必须设置接口的包名--> <property name="basePackage" value="com.gsy.mapper"/> <!--设置使用的sqlSessionFactoryBean的name--> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>
2.根据接口名找到映射文件(接口和映射文件同名且编译后在同一目录)
3.将每一个接口中的方法和映射文件中的sql进行绑定
八、Spring整合Web
在pom.xml引入依赖

<!-- Spring 核心模块的依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.16</version> </dependency> <!-- Spring 和web集成必须的依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency>
配置监听器:
在web.xml中配置监听器,让web项目启动时自动创建Spring容器对象(WebApplicationContext)。

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- 监听ServletContext对象,对象创建时会加载Spring的配置文件,创建Spring容器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
上下文参数值需要带有classpath,表示加载类路径内容。target/classes目录内容。
classpath*:表示当前项目依赖的jar中资源也会寻找。
classpath:后面的值支持星号。例如 applicationContext-*.xml
@WebServlet("/demo") public class DemoServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(req.getServletContext()); People peo = wac.getBean("peo", People.class); System.out.println(peo); } }
九、注解支持
使用注解要在配置文件中配置扫描路径:
<!--多个包用 , 隔开,也可以写上一级的包--> <context:component-scan base-package="com.gsy"/>
1. IoC/DI相关注解
注解名称 | 解释 |
---|---|
@Component | 实例化Bean,默认名称为类名首字母变小写(类名不要出现类似AServiceImpl)。支持自定义名称 |
@Repository | @Component子标签,作用和@Component一样,用在持久层。 |
@Service | @Component子标签,作用和@Component一样,用在业务层。 |
@Controller | @Component子标签,作用和@Component一样,用在控制器层。 |
@Configuration | @Component子标签,作用和@Component一样,用在配置类。 |
@Autowired | 自动注入。默认byType,如果多个同类型bean,使用byName(默认通过属性名查找是否有同名的bean,也可以通过@Qualifier("bean名称"),执行需要注入的Bean名称) |
@Resource | 非Spring注解。默认byName,如果没找到,使用byType。 |
@Repository、@Service、@Controller、@Configuration都是@Component注解的子注解,作用相同。
主要的区别是语义上的区别。不同的注解放在不同层的类中。
虽然不按照语义去做,非把@Service用在持久层,也是有效果的。但为了规范不建议这样做。
1.1 @AutoWired
@AutoWired注解用法十分灵活,可以使用在构造方法、普通方法、属性、方法的参数上
其底层是通过反射实现的自动注入,先byType,如果没有直接报错,如果有多个相同类型,再byName。
如果没有声明无参构造而是自动注入属性的有参构造,则会通过有参构造实现自动注入;
如果有无参构造,会使用反射进行自动注入;
当然也可以指定使用setter方法进行设值注入:只需在对应的set方法上加上@AutoWired注解。
也可以指定有参构造进行注入,也是在对应的构造方法上加@AutoWired注解
例:
1. 底层使用反射直接操作属性完成属性值的自动注入
@Service public class UserServiceImpl { @Autowired UserMapperImpl userMapper; }
2. 底层使用set方法完成属性值的自动注入。
@Service public class UserServiceImpl { UserMapperImpl userMapper; @Autowired public void setUserMapper(UserMapperImpl userMapper) { this.userMapper = userMapper; } }
3. 底层使用构造方法完成属性值的自动注入。
@Service public class UserServiceImpl { UserMapperImpl userMapper; public UserServiceImpl() { } @Autowired public UserServiceImpl(UserMapperImpl userMapper) { this.userMapper = userMapper; } }
十、Spring Test模块
整合后可以在测试类中直接使用Spring容器中的内容,把测试类也放入到Spring容器中,测试类里面可以直接使用注解注入容器中的bean对象。
-
-
junit依赖需要导入的,现在使用的就是Spring Test模块整合的junit功能。
-

<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.16</version> <scope>test</scope> </dependency> </dependencies>
2. 测试
测试类的名字不能为Test,否则会与@Test注解冲突
// 使用Spring整合Junit4的类启动当前测试类(通过配置文件创建spring容器,将测试类交给spring管理) @RunWith(SpringJUnit4ClassRunner.class) // 启动时加载的配置文件,里面要包含classpath @ContextConfiguration(locations = "classpath:applicatonContext.xml") public class MyTest { @Autowired PeopleService peopleService; @Test public void test2(){ peopleService.test(); } }
十一、SpringAOP
1. 代理设计模式
代理模式是Java常见的设计模式之一。
所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
代理设计模式包括:静态代理和动态代理。
静态代理:手动编写代理类。
动态代理:分为JDK动态代理和Cglib动态代理。
其中JDK动态代理是基于接口实现,底层是反射。也就是代理对象和真实对象需要实现相同的接口,JDK动态代理是Java官方提供的技术。
Cglib动态代理是基于继承实现的,底层通过修改字节码文件实现。也就是代理对象需要继承真实对象,Cglib动态代理是第三方的技术,使用的时候需要导入jar包。
1.1 JDK动态代理
接口:
public interface EmpService { List<Emp> allEmp(); }
实现类:
@Service public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper; @Override public List<Emp> allEmp() { List<Emp> emps = empMapper.allEmp(); return emps; } }
jdk动态代理实现为接口创建代理对象:

@Test public void test03() { EmpService empService = (EmpService) Proxy.newProxyInstance(TestEmp.class.getClassLoader(), new Class[]{EmpService.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("动态代理对象实现了接口的方法" + method.getName()); return null; } }); empService.allEmp(); }

@Test public void test04() { ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); EmpService empServiceImpl = (EmpService) classPathXmlApplicationContext.getBean("empServiceImpl"); EmpService empService = (EmpService) Proxy.newProxyInstance( TestEmp.class.getClassLoader(), EmpServiceImpl.class.getInterfaces(), new InvocationHandler() { /* * 参数说明: * 参数1:代理类 * 参数2:目标方法(被代理的方法) * 参数3:方法参数 * */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置功能增强"); // method.invoke()就是调用被代理对象的方法 return method.invoke(empServiceImpl); } }); List<Emp> emps = empService.allEmp(); }
1.2 Cglib动态代理
类:
public class TrueClass { public void testMethod(){ System.out.println("真实类的方法"); } }
Cglib比jdk动态代理多了一个基于继承创建代理对象的功能
演示:

@Test public void test05(){ //创建增强器 Enhancer enhancer = new Enhancer(); //设置被代理类,(代理类继承该类) enhancer.setSuperclass(TrueClass.class); //设置回调方法 enhancer.setCallback(new MethodInterceptor() { /* * 参数说明: * 参数1:o是目标对象(被代理对象) * 参数2:method是目标方法(被代理对象的方法) * 参数3:objects是目标方法的参数 * 参数4:methodProxy是代理方法 * 动态代理对象会自动调用intercept()方法 * */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("前置消息"); methodProxy.invokeSuper(o,objects); System.out.println("后置消息"); return null; } }); //获取代理对象 TrueClass trueClass = (TrueClass) enhancer.create(); trueClass.testMethod(); }
1.3 动态代理总结
2. SpringAOP
2.1 含义(面)
面向切面编程 (AOP) 通过提供另一种思考程序结构的方式来补充面向对象编程 (OOP)。
OOP 中模块化的关键单位是类,而 AOP 中模块化的单位是切面。
切面能够实现跨越多种类型和对象的关注点(例如事务管理)的模块化。(这种关注点在 AOP 文献中通常被称为“横切”关注点。)
Spring 的关键组件之一是 AOP 框架。虽然 Spring IoC 容器不依赖于 AOP(意味着如果您不想使用 AOP,则不需要使用 AOP),但 AOP 补充了 Spring IoC 以提供一个非常强大的中间件解决方案。
强调:
-
-
AOP 是对OOP的补充。
-
AOP的核心是切面。
-
专业术语:
join point:切入点、连接点。就是我们平时说的目标方法,或说对哪个方法做扩展,做增强。
Advice:通知。就是增强内容。
Pointcut:切点。就是表达式(是对join point的描述,通过切点表达式可以找到需要增强功能的方法)
AOP Proxy:代理。Spring支持JDK动态代理和cglib动态代理两种方式,可以通过proxy-target-class=true把默认的JDK动态代理修改为Cglib动态代理。
执行流程:
首先根据PointCut切点找到连接点joinpoint,即这就是需要增强的方法,那就创建代理对象Proxy,将Advice通知weaving织入到joinpoint连接点
AOP主要用于Service层
答:AOP叫做面向切面编程,属于对OOP的扩展。其实现是基于动态代理设计模式,在IoC基础上实现的。
在Spring中提供了两种方式实现AOP:
-
Schema-based:所有的通知都需要实现特定类型的接口实现通知。
-
AspectJ:可以使用普通Java类结合特定的配置实现通知。
2.3 AOP底层代理模式
SpringAOP底层默认为JDK动态代理,如果需要Cglib动态代理
需要引入Cglib的依赖以及在配置文件中开启Cglib动态代理
<aop:aspectj-autoproxy proxy-target-class="true"/>
true代表可以使用Cglib动态代理。
注意:AOP只对被spring托管的类对象有效。
十二、Schema-based方式实现AOP
使用Schema-based方式要先引入相关依赖:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.9.1</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency>
1. Schema-based中通知的分类(面)
-
-
后置通知:在切入点之后执行的增强功能。通知需要实现AfterReturningAdvice接口。
-
环绕通知:一个方法包含了前置通知和后置通知的功能。通知需要实现MethodInterceptor接口。
-
@Component public class MyBefore implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("前置通知"); } }
参数说明:
-
-
args:切入点方法参数。
-
配置切面(见本小节末)
3. 后置通知
后置通知是在切入点之后执行的增强。
新建通知类:MyAfter.java
@Component public class MyAfter implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("目标方法返回值:"+returnValue); System.out.println("后置通知"); } }
参数说明:
-
-
method:切入点方法对象
-
args:切入点方法参数
-
配置切面(见本小节末)
4. 环绕通知
环绕通知可以实现前置通知和后置通知两种功能。
新建通知类:MyAround.java
@Component public class MyAround implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("环绕-前置通知"); Object proceed = invocation.proceed(); System.out.println("环绕-后置通知"); System.out.println("返回值:"+proceed); return proceed; } }
参数说明:
5. 异常通知
异常通知只有在切入点出现异常时才会被触发。如果方法没有异常,异常通知是不会执行的。
@Component public class MyThrow implements ThrowsAdvice { //没要要求必须实现什么方法,但是必须提供下面的方法, // public void afterThrowing 必须相同 //必须有异常对象参数 public void afterThrowing(Exception e){ System.out.println("异常通知:"+e.getMessage()); } }
6. 配置切面
<!-- <aop:aspectj-autoproxy proxy-target-class="true"/>--> <aop:config> <aop:pointcut id="mypoint" expression="execution(* com.gsy.service.EmpServiceImpl.allEmp())"/> <aop:advisor advice-ref="myBefore" pointcut-ref="mypoint"/> <aop:advisor advice-ref="myAfter" pointcut-ref="mypoint"/> <aop:advisor advice-ref="myAround" pointcut-ref="mypoint"/> <aop:advisor advice-ref="myThrow" pointcut-ref="mypoint"/> </aop:config>
切点表达式固定写法:execution(返回值类型 包名.类名.方法名(参数类型,参数类型))
可以使用通配符 * 参数类型可以使用.. 代表任意参数
十三、AspectJ方式实现AOP
1. AspectJ方式通知类型(面)
-
-
后置通知:after。
-
after是否出现异常都执行的后置通知。
-
after-returning切入点不出现异常时才执行的后置通知。
-
-
环绕通知:around。
-
2. 创建通知类
Aspectj方式实现AOP的通知类不需要实现任何的接口,直接声明一个普通java类即可,然后在类中直接定义通知方法即可,方法名随意,但是建议方法名见名知意。

@Component public class MyAdvice { //前置通知方法 public void before(){ System.out.println("我是前置通知方法..."); } //后置通知方法 public void after(){ System.out.println("我是后置通知方法..."); } //环绕通知方法 public Object round(ProceedingJoinPoint pp) throws Throwable { System.out.println("环绕---前"); //放行 Object proceed = pp.proceed(); System.out.println("环绕---后"); return proceed; } //异常通知方法 public void myThrow(Exception e){ System.out.println("我是异常通知......"+e.getMessage()); } }
3. 配置切面
<!--AspectJ方式--> <aop:config> <aop:aspect ref="myAdvice"> <aop:pointcut id="mypoint" expression="execution(* com.gsy.service.EmpServiceImpl.allEmp())"/> <aop:before method="before" pointcut-ref="mypoint"/> <!-- 配置后置通知(没有异常,执行的后置通知 )--> <aop:after-returning method="after" pointcut-ref="mypoint"/> <!--配置后置通知(不管有没有异常都执行后置通知)--> <aop:after method="after" pointcut-ref="mypoint"/> <aop:around method="round" pointcut-ref="mypoint" /> <!-- 配置异常通知 throwing="异常参数名"--> <aop:after-throwing method="myThrow" pointcut-ref="mypoint" throwing="e"/> </aop:aspect> </aop:config>
4. Schema-based和Aspectj的区别
AspectJ方式:是基于配置实现的。通过不同的配置标签告诉Spring通知的类型。AspectJ方式对于通知类写起来比较简单。但是在配置文件中参数和返回值需要特殊进行配置。
5. AspectJ处理有返回值方法及方法参数
如果需要获取切入点方法,建议把通知方法的参数写成和切入点方法一致。
配置文件中的切点表达式需要加 and args(参数名,参数名)参数名和通知方法参数名保持一致
十四、注解实现AOP
注解方式只支持替换AspectJ的XML配置。
-
配置Spring容器注解扫描的路径。
-
<!--配置注解扫描路径--> <context:component-scan base-package="com.bjsxt"/> <!--配置AOP注解生效--> <aop:aspectj-autoproxy expose-proxy="true"/>
作用:
相当于配置文件的bean标签,将某个类的对象扫描到Spring容器中。此注解一般在普通Java类上用。
注意:
默认类名的首字母小写即为bean对象的ID,也可以使用注解的value属性声明自定义的ID,value可以省略不写。
使用:
作用:声明该类为通知类。
作用:声明切点。
作用:声明方法为前置通知方法。
使用:在前置通知方法上声明。
作用:声明方法为后置通知方法。
使用:在后置通知方法上声明。
作用:声明方法为环绕通知方法。
使用:在环绕通知方法上声明。
作用:声明方法为异常通知方法。
使用:在异常通知方法上声明。

@Component @Aspect public class MyAdvice { @Pointcut("execution(* com.gsy.service.EmpServiceImpl.allEmp())") public void pointcut(){} @Before("pointcut()") //前置通知方法 public void before(){ System.out.println("我是前置通知方法..."); } @After("pointcut()") //后置通知方法 public void after(){ System.out.println("我是后置通知方法..."); } @Around("pointcut()") //环绕通知方法 public Object round(ProceedingJoinPoint pp) throws Throwable { System.out.println("环绕---前"); //放行 Object proceed = pp.proceed(); System.out.println("环绕---后"); return proceed; } @AfterThrowing(value = "pointcut()",throwing = "e") //异常通知方法 public void myThrow(Exception e){ System.out.println("我是异常通知......"+e.getMessage()); } }
如果有返回值、参数

@Component @Aspect public class MyAspectJ { @Autowired private LoginLogService loginLogService; @AfterReturning(value = "execution(* com.gsy.service.Impl.UserServiceImpl.login(String,String)) && args(uname,pwd)",returning = "user")//没有异常执行后置通知 public void after(String uname,String pwd,User user){ if(user!=null){ loginLogService.addLog(new LoginLog(0,uname,new Date(),"登录成功")); System.out.println("后置通知:登陆成功"); }else { loginLogService.addLog(new LoginLog(0,uname,new Date(),"登录失败")); System.out.println("后置通知:登陆失败"); } } }
十五、Spring声明式事务
Spring框架发现既然都是固定性代码,就由Spring帮助封装起来。
封装后对外让程序员只需进行简单的XML配置就可以完成事务管理,不再编写事务管理代码。这就是Spring非常重要的功能之一:声明式事务。
1.底层实现
声明式事务是基于AOP实现的。
程序员只需要编写调用持久层代码和业务逻辑代码。把开启事务的代码放在前置通知中,把事务回滚和事务提交的代码放在了后置通知中
2. 使用声明式事务
首先要要引入依赖spring-tx。
<!-- 事务管理包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.16</version> </dependency>
配置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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.gsy"/> <!--spring管理数据源--> <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/ssm?userUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true"/> <property name="username" value="root"/> <property name="password" value="gsy666"/> </bean> <!--spring 管理sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="typeAliasesPackage" value="com.gsy.pojo"/> </bean> <!--spring 管理接口绑定--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.gsy.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!--配置声明式事务--> <!-- 前置通知:开启事务 后置通知:提交事务 异常通知:事务回滚 --> <!--使用注解配置事务,id必须为transactionManager 否则要在注解中手动指定--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--事务通知配置--> <tx:advice id="tx" transaction-manager="transactionManager"> <!--配置方法名,AOP才会织入事务通知--> <tx:attributes> <!-- name的值支持通配符 add*:add开头的方法 *:所有方法 --> <tx:method name="*" /> </tx:attributes> </tx:advice> <!-- 配置AOP 根据切点表达式获取连接点(目标对象的目标方法),为目标对象创建代理对象 将通知和目标方法织入。--> <aop:config> <aop:pointcut id="pt" expression="execution(* com.gsy.service.*.*(..))"/> <aop:advisor advice-ref="tx" pointcut-ref="pt"/> </aop:config> </beans>
3. 使用注解@Transactional
使用注解可以省略事务通知和AOP的配置,但是必须要配置数据源事务管理器
开启注解扫描
<context:component-scan base-package="com.gsy"/>
配置数据源事务管理器、开启事务注解
<!--使用注解配置事务,id必须为transactionManager 否则要在注解中手动指定--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--事务注解生效--> <tx:annotation-driven/>
- @TransactionManager 默认寻找叫做transactionManager的事务管理器。如果没有找到会报异常NoSuchBeanDefinitionException。所以,如果希望配置注解时简单点直接写@Transactional就生效,就必须在XML配置事务管理器时,id必须叫做transactionManager。
- 如果在XML配置事务管理器时,id不叫transactionManager,需要在@Transactional(transactionManager="XML配置时id值")。
- @Transactional用在类上,整个类中方法都生效。
十六、声明式事务四个基础属性
1. name属性
配置哪些方法需要有事务控制,支持*通配符
2.
true:告诉数据库此事务为只读事务。底层支持查询的代码逻辑,不走提交事务和回滚事务的代码,会对性能有一定提升,所以只要是查询的方法,建议设置readonly="true"。
注意:
1. 默认情况下都认为每个方法都是没有事务的(事务自动提交)。
2. 整个调用最终都是在调用者里面统一提交回滚。
3. 在声明式事务中,如果是同一个类的多个方法相互调用,属于同一个事务。
NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行,如果当前有事务,报错。
NESTED:必须在事务状态下执行。如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务(子事务)。
REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起, 重新建个事务。(调用者统一提交回滚)
SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行。
NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起。
MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错。(可以配置在入口方法)
事务A没有提交事务,事务B读取到事务A未提交的数据,这个过程称为脏读。读取到的数据叫做脏数据。
不可重复读:
当事务A读取到表中一行数据时,同时另一个事务修改这行数据,事务A读取到的数据和表中真实数据不一致。
幻读:
DEFAULT:
表示用数据库的隔离级别,MySQL8默认的事务隔离级别REPEATABLE_READ。
READ_UNCOMMITTED:
读未提交(脏读,幻读,不可重复读)。
READ_COMMITTED:
读已提交(幻读,不可重复读)。
REPEATABLE_READ:
可重复读(幻读)。
MySQL采用了MVCC版本控制:
1. 不添加锁的读,快照读(读取的数据状态进行临时存储,快照读读取临时数据),解决幻读
2.加锁读,必须读取数据库中的数据,出现幻读
MySQL中锁机制:
1. 显示锁:1. 执行查询默认不使用锁
2. 查询时使用锁
查询sql for update; 添加排他锁
查询sql lock in share mode; 添加了共享锁
2.自动锁:添加,修改,删除 自动添加排它锁
SERIALIZABLE
串行读来通过牺牲性能解决脏读、不可重复度、幻读问题。
<!--配置参数配置文件路径--> <context:property-placeholder location="classpath:db.properties"/>
使用${}获取数据
<!--配置数据源bean--> <bean id="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${driver}"></property> <property name="url" value="${url}"></property> <property name="username" value="${user}"></property> <property name="password" value="${password}"></property> </bean>
获取属性文件中的值
@Value
作用:用来替换配置文件中的属性注入的。
使用:在属性上声明,值为${“键名”}
注意:使用此注解的注入,无需提供get/set方法。
@Component("u") public class User { @Value("1") private Integer uid; @Value("${driver}") private String uname; private String pwd; ...... }
二十、Bean的生命周期
在这种情况下会调用类的构造方法进行实例化。
-
通过标签的init-method和destory-method自定义初始化和销毁方法。
-
实现各种Aware接口,例如BeanNameAware、BeanFactoryAware、ApplicationContextAware等,可以获取bean名字信息,bean工厂信息,容器信息。
-
通过InitializingBean,DisposableBean实例化Bean和销毁Bean。
-
通过BeanFactoryPostProcessor,BeanPostProcessor进行增强。
1.编写bean的定义信息(xml,注解)
2.通过BeanDefinitionReader 读取bean的定义信息
3.解析出bean的定义信息
4.可以通过BeanFactoryPostProcessor接口实现类,操作bean的定义信息
5.实例化bean对象
6.属性注入
7.可以使用相关的Aware接口,可以获取bean的相关信息,容器信息...
8.可以使用BeanPostProcessor接口中before方法操作对象
9.可以使用init-method调用自定义的初始化方法
10.可以使用BeanPostProcessor接口中after方法操作对象
11.存储到单例池(一级缓存中)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异