网站群建设需求,项目推广方案怎么写,手机网站返回跳转页面代码,咋创建自己的网站文章目录7. 自动装配 (Autowiring)7.1 XML 自动装配7.2 使用注解实现自动装配Autowired vs Resource8. 使用注解开发#xff08;完全体#xff09;8.1 定义 Bean (Component 及其衍生注解)8.2 注入属性 (Value)8.3 注入对象8.4 定义作用域 (Scope)8.5 小结#xff1a;XML vs…
文章目录7. 自动装配 (Autowiring)7.1 XML 自动装配7.2 使用注解实现自动装配Autowired vs Resource8. 使用注解开发完全体8.1 定义 Bean (Component 及其衍生注解)8.2 注入属性 (Value)8.3 注入对象8.4 定义作用域 (Scope)8.5 小结XML vs. 注解9. 使用 Java 配置 (JavaConfig)10、代理模式10.1 静态代理角色分析代码实现优缺点10.2 静态代理再理解代码实现10.3 动态代理代码实现10.4 动态代理再理解通用处理器测试动态代理的好处11、AOPTODO未手敲11.1 什么是AOP11.2 AOP在Spring中的作用11.3 使用Spring实现AOP1. 导入AOP依赖方式一使用Spring原生API接口方式二自定义类来实现AOP方式三使用注解实现**知识点aop:aspectj-autoproxy/**11.4 三种实现 AOP 方式的对比相同点 (Core Principles)不同点 (Evolution Style)12、整合MyBatis整合步骤概览1. 导入Maven依赖2. 配置Maven静态资源过滤12.1 回忆MyBatis1. 实体类 (User.java)2. MyBatis核心配置文件 (mybatis-config.xml)3. Mapper接口 (UserMapper.java)4. Mapper XML文件 (UserMapper.xml)5. 测试类12.2 MyBatis-Spring核心概念12.3 整合实现方式一SqlSessionTemplate1. 创建Spring数据层配置文件 (spring-dao.xml)2. 创建Mapper实现类 (UserMapperImpl.java)3. 创建Spring主配置文件 (applicationContext.xml)4. 测试12.4 整合实现方式二SqlSessionDaoSupport1. 修改Mapper实现类 (UserMapperImpl2.java)2. 修改Spring配置 (applicationContext.xml)3. 测试 (类似方式一)13、声明式事务13.1 回顾事务13.2 事务失效场景测试13.3 Spring中的事务管理配置声明式事务XML方式再次测试写在最后参考你好我是 ZzzFatFish欢迎回到我的 Spring 学习笔记。在 循序渐进学 Spring (上)从 IoC/DI 核心原理到 XML 配置实战 中我们深入探讨了 Spring 的核心 IoC/DI 以及 XML 配置的各种方式。这次我们将更进一步探索如何让 Spring 变得更“聪明”从自动装配开始逐步走向现代化的注解和 Java 配置并最终揭开 AOP 的神秘面纱完成与 MyBatis 的整合实战。
7. 自动装配 (Autowiring)
自动装配是 Spring IoC 容器的一项强大功能它可以自动满足 Bean 之间的依赖关系减少 XML 中的显式配置。
7.1 XML 自动装配
通过在 bean 标签上设置 autowire 属性实现。
环境准备
public class Cat { public void shout() { System.out.println(喵~); } }
public class Dog { public void shout() { System.out.println(汪~); } }
public class People {private Cat cat;private Dog dog;private String name;// ... getters and setters and toString
}beans.xml
bean idcat classcom.github.subei.pojo.Cat/
bean iddog classcom.github.subei.pojo.Dog/!-- 手动装配 (传统方式) --
bean idpeopleManual classcom.github.subei.pojo.Peopleproperty namecat refcat/property namedog refdog/
/bean!-- 手动装配 (传统方式2) --
bean idpeople classcom.github.pojo.People p:cat-refcat p:dog-refdog/!-- 自动装配 byName --
bean idpeopleByName classcom.github.subei.pojo.People autowirebyName/!-- 自动装配 byType --
bean idpeopleByType classcom.github.subei.pojo.People autowirebyType/autowirebyName: Spring 会在容器中查找 id 与 People 类中属性名cat, dog相同的 Bean并进行注入。autowirebyType: Spring 会在容器中查找与 People 类中属性类型Cat, Dog相匹配的 Bean并进行注入。 注意使用 byType 时容器中同类型的 Bean 必须是唯一的否则会抛出异常。
public class MyTest {Testpublic void test(){ClassPathXmlApplicationContext context new ClassPathXmlApplicationContext(beans.xml);People people context.getBean(people, People.class);people.getCat().shout();people.getDog().shout();}
}7.2 使用注解实现自动装配
注解是目前更主流的自动装配方式它将配置信息直接写在 Java 类中更为便捷。
步骤 1开启注解支持 在 XML 配置文件中需要添加 context 命名空间并开启注解扫描。
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocation...!-- 开启基于注解的配置它会激活 Autowired, Resource 等注解 --context:annotation-config/!-- 仍然需要将 Bean 定义在 XML 中 --bean idcat classcom.github.subei.pojo.Cat/bean iddog classcom.github.subei.pojo.Dog/bean idpeople classcom.github.subei.pojo.People//beans步骤 2在类中使用注解
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.annotation.Resource;public class People {Autowired // 1. 使用 AutowiredQualifier(cat) // 2. 当有多个同类型Bean时用 Qualifier 指定名称private Cat cat;// Autowired// private Dog dog;Resource(name dog) // 3. 使用 Resource (更推荐)private Dog dog;// ...
}Autowired vs Resource
这是非常重要的一个区别点
特性Autowired (Spring 提供)Resource (JSR-250, Java 标准)装配顺序1. 按类型 (byType) 在容器中查找。1. 按名称 (byName) 查找。2. 如果找到多个再按名称 (byName) 查找。2. 如果按名称找不到再按类型 (byType) 查找。3. 如果仍未确定可配合 Qualifier(beanId) 使用。3. 如果按类型也找不到或找到多个则报错。依赖强依赖 Spring 框架。是 Java 标准减少了对 Spring 的耦合。常用场景简单场景或需要 Qualifier 精准控制时。推荐使用其装配顺序更符合直觉更明确。
Autowired(required false)可以标注某个属性不是必须的如果容器中找不到对应的 Bean该属性为 null 而不会报错。
观点有人认为“注解一时爽维护火葬场”是因为当项目庞大、依赖关系复杂时注解分散在各个类中不如 XML 集中管理来得清晰。但在现代开发中注解的便利性已成为主流配合良好的设计可以很好地管理。 8. 使用注解开发完全体
之前的注解只是用于自动装配Bean本身还是在XML中定义的。现在我们学习使用注解来定义Bean从而可以完全替代XML。
步骤 1开启组件扫描 使用 context:component-scan 来代替 context:annotation-config/。它不仅会开启注解支持还会扫描指定包下的类并将带有特定注解的类自动注册为 Bean。
?xml version1.0 encodingUTF-8 ?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd!--指定要扫描的包Spring 会自动查找这个包及其子包下所有带注解的类。这个标签包含了 context:annotation-config/ 的功能。
--context:component-scan base-packagecom.github.pojo//beans步骤 2使用注解定义 Bean 及其属性
8.1 定义 Bean (Component 及其衍生注解)
Component: 通用的组件注解表示这个类是一个由 Spring 管理的 Bean。默认的 beanId 是类名首字母小写如 User - user。 Component(customId) 可以自定义 beanId。
为了更好地标识分层架构Component 有三个衍生注解它们在功能上完全相同但语义更清晰
Repository: 用于标注数据访问层 (DAO) 的组件。Service: 用于标注业务逻辑层 (Service) 的组件。Controller: 用于标注表现层 (Controller) 的组件。
import org.springframework.stereotype.Component;Component // 等价于 bean iduser classcom.github.subei.pojo.User/
public class User {// 相当于 property namename valuesubeiLY/Value(subeiLY)public String name;// ...
}8.2 注入属性 (Value)
Value 注解用于注入基本类型和 String 类型的值。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;Component
public class User {// 相当于 property namename valuesubeiLY/Value(subeiLY)public String name;
}8.3 注入对象
使用 Autowired 或 Resource与第 7 节相同。
8.4 定义作用域 (Scope)
使用 Scope 注解来定义 Bean 的作用域。
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;Component
Scope(prototype) // 等价于 bean ... scopeprototype/
public class User { ... }8.5 小结XML vs. 注解
XML配置集中一目了然适用于所有场景。但当 Bean 数量多时会很繁琐。注解配置分散在代码中开发便捷。但对于第三方库的类我们无法修改源码则无法使用注解。
最佳实践混合使用
XML负责整体配置如数据源、事务管理器、组件扫描 context:component-scan 等。注解负责业务类Controller, Service, DAO的 Bean 定义和依赖注入。 9. 使用 Java 配置 (JavaConfig)
从 Spring 3.0 开始官方提供了完全基于 Java 类来进行配置的方式可以彻底摆脱 XML 文件。
步骤 1创建配置类 配置类使用 Configuration 标注它本身也是一个 Component。
package com.github.subei.config;import com.github.subei.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;Configuration // 声明这是一个配置类替代 beans.xml
ComponentScan(com.github.subei.pojo) // 扫描组件等同于 context:component-scan
Import(AnotherConfig.class) // 导入其他配置类等同于 import
public class MyConfig {// Bean 注解表示这个方法将返回一个对象该对象将被注册为 Spring 容器中的 Bean。// 方法名 getUser 默认成为 bean 的 id。// 返回值类型 User 相当于 bean 标签的 class 属性。Beanpublic User getUser() {return new User(); // 返回要注入到容器中的对象}
}步骤 2创建实体类可以与JavaConfig配合使用
package com.github.subei.pojo;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;// Component注解可以让 ComponentScan 扫描到
Component
public class User {private String name;public String getName() { return name; }Value(KANGSHIFU) // 注入属性值public void setName(String name) { this.name name; }// ... toString
}步骤 3测试 当完全使用 Java 配置时需要用 AnnotationConfigApplicationContext 来加载容器。
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class MyTest {public static void main(String[] args) {// 通过 AnnotationConfigApplicationContext 来获取容器参数是配置类的 Class 对象ApplicationContext context new AnnotationConfigApplicationContext(MyConfig.class);// beanId 默认为方法名 getUserUser user (User) context.getBean(getUser);System.out.println(user.getName());}
}好的我已经帮你把笔记重新整理和排版并针对你遇到的问题进行了一些补充说明。我保留了你所有的原始内容包括你的个人备注只是让整体结构更清晰、更易于阅读。 10、代理模式 为什么要学习代理模式 因为这就是 Spring AOP 的底层实现原理 代理模式的分类
静态代理动态代理
10.1 静态代理
角色分析
抽象角色 (Subject)一般会使用接口或者抽象类来定义。真实角色 (Real Subject)被代理的角色真正执行业务逻辑的类。代理角色 (Proxy)代理真实角色在真实角色执行前后可以附加一些操作。客户 (Client)访问代理角色的人。
代码实现
1. 接口 (Rent.java)
package com.github.subei.demo;// 租房
public interface Rent {public void rent();
}2. 真实角色 (Host.java)
package com.github.subei.demo;// 房东
public class Host implements Rent{public void rent(){System.out.println(房东要出租房子);}
}3. 代理角色 (Proxy.java)
package com.github.subei.demo;public class Proxy implements Rent { // 注意代理类也应该实现同一个接口private Host host;public Proxy() {}public Proxy(Host host) {this.host host;}public void rent(){seeHouse();host.rent(); // 调用真实角色的方法contract();fare();}// 看房public void seeHouse(){System.out.println(中介带你看房);}// 收中介费public void fare(){System.out.println(收中介费);}// 签合同public void contract(){System.out.println(和你签合同);}
}4. 客户端 (Client.java)
package com.github.subei.demo;public class Client {public static void main(String[] args) {// 房东要租房子Host host new Host();// 代理中介帮房东租房子但是代理角色一般会有一些附属操作Proxy proxy new Proxy(host);// 我们不直接找房东而是直接找中介租房proxy.rent();}
}优缺点
优点: 可以使得我们的真实角色更加纯粹不再去关注一些公共的业务。公共的业务由代理来完成实现了业务的分工。公共业务发生扩展时方便集中管理。 缺点: 一个真实角色就会产生一个代理角色代码量会翻倍开发效率会变低。
10.2 静态代理再理解
以用户管理业务为例日志功能就是可以被代理的公共业务。
代码实现
1. 抽象角色 (UserService.java)
package com.github.subei.demo2;// 实现增删改查业务
public interface UserService {void add();void delete();void update();void query();
}2. 真实角色 (UserServiceImpl.java)
package com.github.subei.demo2;public class UserServiceImpl implements UserService {public void add() {System.out.println(添加用户);}public void delete() {System.out.println(删除用户);}public void update() {System.out.println(更新用户);}public void query() {System.out.println(查询用户);}
}3. 代理角色 (UserServiceProxy.java)
package com.github.subei.demo2;public class UserServiceProxy implements UserService {private UserServiceImpl userService;public void setUserService(UserServiceImpl userService) {this.userService userService;}public void add() {log(add);userService.add();}public void delete() {log(delete);userService.delete();}public void update() {log(update);userService.update();}public void query() {log(query);userService.query();}// 日志方法public void log(String msg){System.out.println(执行了 msg 方法);}
}4. 客户端 (Client.java)
package com.github.subei.demo2;public class Client {public static void main(String[] args) {// 真实业务对象UserServiceImpl userService new UserServiceImpl();// 代理类UserServiceProxy proxy new UserServiceProxy();// 设置要代理的真实对象proxy.setUserService(userService);proxy.add();proxy.query();}
}思考 这种开发模式像是纵向的业务开发中切入了横向的功能如日志。 我们想要静态代理的好处又不想要它的缺点于是就有了 动态代理 10.3 动态代理
动态代理和静态代理的角色一样。动态代理的代理类是动态生成的不是我们直接写好的。动态代理分类 基于接口的动态代理 — JDK 动态代理【本例使用】基于类的动态代理 — CGLIBJava 字节码实现 — Javassist 需要了解两个核心类: java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。 代码实现
1. 抽象角色 (Rent.java)
package com.github.subei.demo3;public interface Rent {void rent();
}2. 真实角色 (Host.java)
package com.github.subei.demo3;public class Host implements Rent {public void rent(){System.out.println(房东要出租房子);}
}3. 代理处理程序 (ProxyInvocationHandler.java)
package com.github.subei.demo3;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 会用这个类自动生成代理类
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);}// 处理代理实例并返回代理结果// 这个方法是代理对象调用任何接口方法时都会执行的public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 动态代理的本质就是利用反射机制seeHouse();// 调用真实对象的方法Object result method.invoke(rent, args);fare();return result;}// 附加操作public void seeHouse(){System.out.println(中介带你看房);}public void fare(){System.out.println(收中介费);}
}4. 客户端 (Client.java)
package com.github.subei.demo3;public class Client {public static void main(String[] args) {// 真实角色Host host new Host();// 代理角色现在没有具体的代理类只有一个处理器ProxyInvocationHandler handler new ProxyInvocationHandler();// 通过调用程序来处理我们要调用的接口对象handler.setRent(host);// 动态生成对应的代理类Rent proxy (Rent) handler.getProxy(); proxy.rent();}
}核心 一个动态代理处理器一般代理某一类业务可以代理实现了同一接口的多个类。 10.4 动态代理再理解
我们可以编写一个通用的动态代理处理器让它能代理任何对象。
通用处理器
package com.github.subei.Demo4;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 通用的动态代理处理器
public class ProxyInvocationHandler implements InvocationHandler {// 被代理的对象设置为Object类型private Object target;public void setTarget(Object target){this.target target;}// 生成得到代理类public Object getProxy(){return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}// 处理代理实例并返回代理结果public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log(method.getName());Object result method.invoke(target, args);return result;}public void log(String methodName){System.out.println(执行了 methodName 方法);}
}测试
package com.github.subei.Demo4;import com.github.subei.demo2.UserService;
import com.github.subei.demo2.UserServiceImpl;public class Client {public static void main(String[] args) {// 真实角色UserServiceImpl userService new UserServiceImpl();// 代理角色处理器ProxyInvocationHandler pih new ProxyInvocationHandler();// 设置要代理的对象pih.setTarget(userService);// 动态生成代理类UserService proxy (UserService)pih.getProxy();proxy.add();}
}动态代理的好处
使得我们的真实角色更加纯粹不再关注公共业务。公共业务由代理完成实现了业务分工。公共业务发生扩展时方便集中管理。一个动态代理处理器可以代理多个类只要它们实现了接口。 11、AOPTODO未手敲
11.1 什么是AOP
AOPAspect Oriented Programming意为面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP面向对象编程的延续是软件开发中的一个热点也是Spring框架中的一个重要内容是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性同时提高了开发的效率。
11.2 AOP在Spring中的作用
提供声明式事务允许用户自定义切面。
AOP 核心概念
横切关注点 (Cross-cutting concerns) 跨越应用程序多个模块的功能。即与我们业务逻辑无关但我们又需要关注的部分就是横切关注点。如日志、安全、缓存、事务等等。切面 (Aspect) 横切关注点被模块化的特殊对象。在代码中通常是一个类。通知 (Advice) 切面必须要完成的工作即切面类中的方法。目标 (Target) 被通知的对象即被代理的真实对象。代理 (Proxy) 向目标对象应用通知之后创建的对象。切入点 (Pointcut) 切面通知**执行的“地点”**的定义。连接点 (JoinPoint) 与切入点匹配的执行点在Spring中连接点就是方法的执行。 Spring AOP 中通过 Advice 定义横切逻辑Spring 支持 5 种类型的 Advice。 核心思想 AOP 就是在不改变原有代码的情况下去增加新的功能。 11.3 使用Spring实现AOP
1. 导入AOP依赖
使用AOP织入需要导入aspectjweaver依赖包。
dependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactIdversion1.9.4/version !-- 版本号可根据项目情况调整 --
/dependency方式一使用Spring原生API接口
1. 业务接口和实现类
// UserService.java
package com.github.subei.service;
public interface UserService {void add();void delete();void select();void update();
}// UserServiceImpl.java
package com.github.subei.service;
public class UserServiceImpl implements UserService{public void add() { System.out.println(增加了一个用户); }public void delete() { System.out.println(删除了一个用户); }public void select() { System.out.println(查询了一个用户); }public void update() { System.out.println(更新了一个用户); }
}2. 前置/后置增强类
// Log.java (前置通知)
package com.github.subei.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;public class Log implements MethodBeforeAdvice {// method: 要执行的目标对象的方法// args: 参数// target: 目标对象public void before(Method method, Object[] args, Object target) throws Throwable {System.out.println(target.getClass().getName() 的 method.getName() 方法被执行了);}
}// AfterLog.java (后置通知)
package com.github.subei.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;public class AfterLog implements AfterReturningAdvice {// returnValue: 返回值public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {System.out.println(执行了 target.getClass().getName() 的 method.getName() 方法返回结果为: returnValue);}
}3. Spring配置 (applicationContext.xml)
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd!-- 注册bean --bean iduserService classcom.github.subei.service.UserServiceImpl/bean idlog classcom.github.subei.log.Log/bean idafterLog classcom.github.subei.log.AfterLog/!-- 方式一: 使用原生Spring API接口 --aop:config!-- 切入点: expression:表达式, execution(要执行的位置* * * *) --aop:pointcut idpointcut expressionexecution(* com.github.subei.service.UserServiceImpl.*(..))/!-- 配置通知器 aop:advisor --aop:advisor advice-reflog pointcut-refpointcut/aop:advisor advice-refafterLog pointcut-refpointcut//aop:config/beans4. 测试
import com.github.subei.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest {public static void main(String[] args) {ApplicationContext context new ClassPathXmlApplicationContext(applicationContext.xml);// 动态代理代理的是接口所以要用接口类型接收UserService userService context.getBean(userService, UserService.class);userService.select();}
}方式二自定义类来实现AOP
1. 编写自定义切面类 (POJO)
package com.github.subei.diy;public class DiyPointCut {public void before(){System.out.println(---------方法执行前---------);}public void after(){System.out.println(---------方法执行后---------);}
}或
public class DiyPointCut {// 前置通知方法现在可以接收一个JoinPoint对象public void before(JoinPoint jp) {System.out.println(---------方法执行前---------);// 1. 获取目标对象System.out.println(目标对象: jp.getTarget());// 2. 获取方法签名从而得到方法名和类名System.out.println(拦截的方法: jp.getSignature().getName());System.out.println(方法所属类: jp.getSignature().getDeclaringTypeName());// 3. 获取方法的参数System.out.println(方法参数: Arrays.toString(jp.getArgs()));}// 后置通知方法同样可以获取这些信息public void after(JoinPoint jp) {System.out.println(---------方法执行后---------);System.out.println(方法 jp.getSignature().getName() 执行完毕。);}
}2. Spring配置
!-- 注册自定义切面 bean --
bean iddiy classcom.github.subei.diy.DiyPointCut/aop:config!-- 使用 aop:aspect 定义切面 --aop:aspect refdiy!-- 定义切入点 --aop:pointcut idpoint expressionexecution(* com.github.subei.service.UserServiceImpl.*(..))/!-- 定义通知 --aop:before methodbefore pointcut-refpoint /aop:after methodafter pointcut-refpoint//aop:aspect
/aop:config3. 测试 (同上)
方式三使用注解实现
1. 编写注解实现的切面类
package com.github.subei.diy;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;// 使用注解方式实现AOP
Aspect // 标注这个类是一个切面
public class AnnotationPointCut {Before(execution(* com.github.subei.service.UserServiceImpl.*(..)))public void before(){System.out.println(---------方法执行前(注解)---------);}After(execution(* com.github.subei.service.UserServiceImpl.*(..)))public void after(){System.out.println(---------方法执行后(注解)---------);}// 在环绕增强中我们可以给定一个参数代表我们要获取处理切入的点Around(execution(* com.github.subei.service.UserServiceImpl.*(..)))public void around(ProceedingJoinPoint jp) throws Throwable {System.out.println(环绕前);Signature signature jp.getSignature(); // 获得签名System.out.println(签名: signature);// 执行目标方法: proceedObject proceed jp.proceed();System.out.println(环绕后);System.out.println(执行结果: proceed);}
}2. Spring配置文件
!-- 第三种方法: 使用注解方式实现 --!-- 1. 注册带有Aspect注解的切面类 --
bean idannotationPointCut classcom.github.subei.diy.AnnotationPointCut/!-- 2. 开启注解支持(这会让Spring自动寻找Aspect注解的bean并创建代理) --
aop:aspectj-autoproxy/知识点aop:aspectj-autoproxy/
通过AOP命名空间的 aop:aspectj-autoproxy / 声明可以自动为Spring容器中那些配置了 AspectJ 切面的bean创建代理织入切面。它有一个 proxy-target-class 属性默认为 false proxy-target-classfalse (默认): 使用 JDK动态代理 织入增强。目标类必须实现接口。proxy-target-classtrue: 使用 CGLIB动态代理 技术织入增强。即使目标类没有实现接口也可以创建代理。注意 即使 proxy-target-class 设置为 false如果目标类没有声明任何接口Spring 将自动切换到使用 CGLIB。
11.4 三种实现 AOP 方式的对比
对比维度 (Feature)方式一Spring API接口方式二自定义类 XML配置方式三注解方式 (AspectJ)实现方式切面类必须实现Spring特定的接口如 MethodBeforeAdvice。切面类是一个普通的Java类 (POJO)不需要实现任何接口。切面类是一个普通的Java类 (POJO)但使用 Aspect 注解标识。与Spring框架的耦合度高。代码强依赖于org.springframework.aop.*包可移植性差。低。切面类本身完全不依赖Spring可以独立存在。中等。依赖于org.aspectj.*注解包但与Spring核心API解耦。配置方式完全通过XML配置。使用aop:advisor标签将通知和切入点绑定。完全通过XML配置。使用aop:aspect标签引用切面Bean并指定方法。XML中只需开启注解支持aop:aspectj-autoproxy/具体逻辑在Java类中通过注解完成。易用性与可读性差。代码最繁琐需要为不同类型的通知创建不同的类结构分散。中等。逻辑与配置分离需要来回查看XML和Java文件才能理解整体。好。逻辑、切入点、通知类型都集中在一个类中代码即配置内聚性高可读性最好。功能强大性较弱。仅提供基本的前置、后置等通知功能有限。较强。支持前置、后置、环绕、异常等所有通知类型但配置在XML中。最强。完全支持AspectJ的所有功能特别是Around环绕通知可以精确控制目标方法的执行。当前主流用法已过时基本不再使用。在一些需要将AOP配置与业务代码完全解耦的遗留项目中可能见到。绝对主流是当前Spring/Spring Boot项目开发的首选和推荐方式。
相同点 (Core Principles)
核心思想一致三种方式都是为了实现AOP面向切面编程将横切关注点如日志、事务与业务逻辑代码分离。底层实现一致它们的底层都依赖于Spring在运行时创建动态代理对象JDK动态代理或CGLIB代理来织入切面逻辑。依赖一致都需要在项目中引入 aspectjweaver 这个依赖包。切入点表达式一致定义切入点时使用的 execution() 表达式语法是完全相同的。
不同点 (Evolution Style) 最大区别在于“耦合度”和“配置风格” 方式一是强耦合、纯XML配置。代码和Spring API绑死。方式二是低耦合、纯XML配置。代码是干净的POJO但AOP的“身份”和行为完全由外部XML赋予。方式三是中等耦合、注解驱动。代码通过注解自我声明其AOP“身份”和行为XML只负责开启总开关。 代码的侵入性不同 方式一侵入性最强因为它强制你的类去实现它的接口。方式二和方式三对业务代码都是非侵入式的这也是AOP提倡的。 发展趋势和推荐度不同 这三种方式清晰地展示了Spring AOP的演进路线从早期与框架紧密绑定的API到配置与代码分离再到最终使用注解将配置与逻辑内聚。方式三注解无疑是目前最佳的实践因为它兼顾了低耦合、高可读性和强大的功能。
简单来说你可以这样理解它们的演进
方式一“你代码必须听我的框架按我的规矩来写。”方式二“你代码做你自己的事我XML来告诉你什么时候该做什么。”方式三“你代码自己决定自己该做什么并告诉大家通过注解我框架负责让你生效就行。” 12、整合MyBatis
整合步骤概览
导入相关Jar包编写配置文件编写代码与测试
1. 导入Maven依赖
!-- JUnit 测试框架 --
dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/version
/dependency!-- MyBatis 核心包 --
dependencygroupIdorg.mybatis/groupIdartifactIdmybatis/artifactIdversion3.5.3/version
/dependency!-- MySQL 数据库驱动 --
dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.47/version
/dependency!-- Spring 相关包 --
dependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion5.2.12.RELEASE/version
/dependency
dependencygroupIdorg.springframework/groupIdartifactIdspring-jdbc/artifactIdversion5.1.10.RELEASE/version
/dependency!-- AOP 织入器 --
dependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactIdversion1.9.6/version
/dependency!-- 【关键】MyBatis-Spring 整合包 --
dependencygroupIdorg.mybatis/groupIdartifactIdmybatis-spring/artifactIdversion2.0.6/version
/dependency2. 配置Maven静态资源过滤
为了确保 src/main/java 目录下的 .xml 和 .properties 文件能被正确打包需要在 pom.xml 中添加以下配置
buildresourcesresourcedirectorysrc/main/java/directoryincludesinclude**/*.properties/includeinclude**/*.xml/include/includesfilteringtrue/filtering/resource/resources
/build12.1 回忆MyBatis
在整合前我们先回顾一下原生MyBatis的开发流程。
1. 实体类 (User.java)
package com.github.subei.pojo;
import lombok.Data;Data
public class User {private int id;private String name;private String pwd;// ... Constructors, Getters, Setters, toString() ...
}2. MyBatis核心配置文件 (mybatis-config.xml)
?xml version1.0 encodingUTF-8 ?
!DOCTYPE configurationPUBLIC -//mybatis.org//DTD Config 3.0//ENhttp://mybatis.org/dtd/mybatis-3-config.dtd
configurationtypeAliasespackage namecom.github.subei.pojo//typeAliasesenvironments defaultdevelopmentenvironment iddevelopmenttransactionManager typeJDBC/dataSource typePOOLEDproperty namedriver valuecom.mysql.jdbc.Driver/property nameurl valuejdbc:mysql://localhost:3306/mybatis?characterEncodingUTF-8/property nameusername valueroot/property namepassword valueroot//dataSource/environment/environmentsmapperspackage namecom.github.subei.mapper//mappers
/configuration3. Mapper接口 (UserMapper.java)
package com.github.subei.mapper;
import com.github.subei.pojo.User;
import java.util.List;public interface UserMapper {ListUser selectUser();
}4. Mapper XML文件 (UserMapper.xml)
?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapperPUBLIC -//mybatis.org//DTD Config 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.github.subei.mapper.UserMapperselect idselectUser resultTypeUserselect * from user;/select
/mapper5. 测试类
public class MyTest {Testpublic void selectUser() throws IOException {String resource mybatis-config.xml;InputStream inputStream Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession sqlSessionFactory.openSession();try {UserMapper mapper sqlSession.getMapper(UserMapper.class);ListUser userList mapper.selectUser();for(User user : userList){System.out.println(user);}} finally {sqlSession.close();}}
}12.2 MyBatis-Spring核心概念 官方文档地址: http://www.mybatis.org/spring/zh/index.html MyBatis-Spring 的核心目标是帮助我们将 MyBatis 无缝地整合到 Spring 容器中让 Spring 来管理 MyBatis 的组件。 SqlSessionFactoryBean 在 Spring 中我们不再使用 SqlSessionFactoryBuilder而是使用 SqlSessionFactoryBean 来创建 SqlSessionFactory。它负责读取配置并创建一个由 Spring 管理的 SqlSessionFactory 实例。其最重要的属性是 dataSource用于接收 Spring 管理的数据源。 SqlSessionTemplate SqlSessionTemplate 是 MyBatis-Spring 的核心它是 SqlSession 的一个线程安全实现。它能自动参与到 Spring 的事务管理中负责 session 的生命周期获取、提交/回滚、关闭。在整合后我们应该始终使用 SqlSessionTemplate 来代替原生的 DefaultSqlSession。
12.3 整合实现方式一SqlSessionTemplate
1. 创建Spring数据层配置文件 (spring-dao.xml)
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd!-- 1. 配置数据源 (DataSource) --bean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSourceproperty namedriverClassName valuecom.mysql.jdbc.Driver /property nameurl valuejdbc:mysql://localhost:3306/mybatis?characterEncodingUTF-8/property nameusername valueroot/property namepassword valueroot//bean!-- 2. 配置 SqlSessionFactory --bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBeanproperty namedataSource refdataSource /!-- 关联MyBatis核心配置文件 (可选用于settings, typeAliases等) --property nameconfigLocation valueclasspath:mybatis-config.xml/!-- 扫描Mapper XML文件 --property namemapperLocations valueclasspath:com/github/subei/mapper/*.xml//bean!-- 3. 注册 SqlSessionTemplate --bean idsqlSessionTemplate classorg.mybatis.spring.SqlSessionTemplate!-- 只能使用构造器注入sqlSessionFactory因为它没有set方法 --constructor-arg index0 refsqlSessionFactory//bean
/beans2. 创建Mapper实现类 (UserMapperImpl.java)
package com.github.subei.mapper;
import com.github.subei.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;public class UserMapperImpl implements UserMapper {// 我们的所有操作都使用SqlSessionTemplate来执行private SqlSessionTemplate sqlSession;public void setSqlSession(SqlSessionTemplate sqlSession) {this.sqlSession sqlSession;}public ListUser selectUser() {UserMapper mapper sqlSession.getMapper(UserMapper.class);return mapper.selectUser();}
}3. 创建Spring主配置文件 (applicationContext.xml)
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd!-- 导入数据层配置 --import resourcespring-dao.xml/!-- 注册Mapper实现类的Bean --bean iduserMapper classcom.github.subei.mapper.UserMapperImplproperty namesqlSession refsqlSessionTemplate//bean
/beans4. 测试
Test
public void testSelectUser() {ApplicationContext context new ClassPathXmlApplicationContext(applicationContext.xml);UserMapper userMapper context.getBean(userMapper, UserMapper.class);for (User user : userMapper.selectUser()){System.out.println(user);}
}结果成功输出 此时原生的 mybatis-config.xml 文件中的数据源和事务管理器配置已被 Spring 完全接管可以被简化。
12.4 整合实现方式二SqlSessionDaoSupport
这是一种更便捷的方式通过继承 SqlSessionDaoSupport 类来简化 Mapper 实现类的编写。
1. 修改Mapper实现类 (UserMapperImpl2.java)
package com.github.subei.mapper;
import com.github.subei.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {public ListUser selectUser() {// 直接通过 getSqlSession() 获取 SqlSessionTemplateUserMapper mapper getSqlSession().getMapper(UserMapper.class);return mapper.selectUser();}
}2. 修改Spring配置 (applicationContext.xml)
SqlSessionDaoSupport 需要注入 SqlSessionFactory 而不是 SqlSessionTemplate。
bean iduserMapper2 classcom.github.subei.mapper.UserMapperImpl2property namesqlSessionFactory refsqlSessionFactory /
/bean3. 测试 (类似方式一)
Test
public void testSelectUser2() {ApplicationContext context new ClassPathXmlApplicationContext(applicationContext.xml);UserMapper userMapper context.getBean(userMapper2, UserMapper.class);// ...
}总结整合到Spring后可以完全移除MyBatis配置文件中的数据源和事务配置。除了XML配置还可以使用注解方式实现整合这是现代开发中更主流的方式。 13、声明式事务
13.1 回顾事务
事务 是一组不可分割的业务操作单元要么都成功要么都失败。它用于保证数据的完整性和一致性。
事务的ACID原则
原子性 (Atomicity)事务中的所有操作是一个整体不可分割。一致性 (Consistency)事务完成后数据必须保持业务规则上的一致状态。隔离性 (Isolation)多个事务并发执行时应相互隔离防止数据损坏。持久性 (Durability)事务一旦提交其结果就是永久性的。
13.2 事务失效场景测试
假设我们在一个方法内先执行一个成功的插入操作再执行一个失败的删除操作SQL语法错误。
1. 扩展Mapper接口和XML
// UserMapper.java
public interface UserMapper {ListUser selectUser();int addUser(User user);int deleteUser(int id);
}!-- UserMapper.xml --
insert idaddUser ...insert into user (id,name,pwd) values (#{id},#{name},#{pwd});
/insert
!-- 故意写错SQL将 delete 写成 deletes --
delete iddeleteUser ...deletes from user where id #{id};
/delete2. 在一个方法中调用
// UserMapperImpl.java
public ListUser selectUser() {UserMapper mapper getSqlSession().getMapper(UserMapper.class);// 先添加mapper.addUser(new User(6,维维,123456));// 再删除这个会失败mapper.deleteUser(6); return mapper.selectUser();
}测试结果程序会因SQL异常而中断但数据库中用户添加成功了这破坏了数据的一致性因为我们期望添加和删除是一个整体。
13.3 Spring中的事务管理
Spring 提供了两种事务管理方式
编程式事务在业务代码中手动控制事务的开启、提交、回滚。侵入性强不推荐。声明式事务通过配置XML或注解来管理事务业务代码无需关心事务逻辑。这是我们使用的重点。
配置声明式事务XML方式
1. 配置事务管理器 首先需要一个事务管理器 DataSourceTransactionManager并关联我们的数据源。
!-- applicationContext.xml --!-- 1. 配置声明式事务管理器 --
bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refdataSource /
/bean2. 配置事务通知 (tx:advice) 在这里定义事务的规则比如哪些方法需要事务以及事务的传播特性。 需要先引入 tx 命名空间及其约束。
!-- 2. 配置事务通知 --
tx:advice idtxAdvice transaction-managertransactionManagertx:attributes!-- 配置哪些方法使用什么样的事务, propagation是传播特性REQUIRED: 如果当前没有事务就新建一个如果已存在就加入。这是最常用的。--tx:method nameadd* propagationREQUIRED/tx:method namedelete* propagationREQUIRED/tx:method nameupdate* propagationREQUIRED/tx:method nameselect* read-onlytrue/tx:method name* propagationREQUIRED//tx:attributes
/tx:advice3. 配置AOP将事务织入 使用AOP将事务通知应用到指定的方法上。
!-- 3. 配置AOP --
aop:config!-- 定义切入点这里是对mapper包下的所有方法 --aop:pointcut idtxPointcut expressionexecution(* com.github.subei.mapper.*.*(..))/!-- 将事务通知和切入点绑定 --aop:advisor advice-reftxAdvice pointcut-reftxPointcut/
/aop:config再次测试
在配置好声明式事务后再次运行之前的测试代码。 结果程序依然会报错但是数据库中新用户没有被添加事务成功回滚保证了数据的一致性。 为什么需要事务 保证数据一致性防止因部分操作失败而导致数据状态混乱。简化开发使用Spring的声明式事务开发者可以专注于业务逻辑而无需手动管理复杂的事务代码。 写在最后
从 IoC/DI 的核心原理到 XML、注解、JavaConfig 三种配置方式的演进从代理模式的底层铺垫到 AOP 的切面思想再到最后整合 MyBatis 并加上事务的保障。至此我们已经走完了 Spring Framework 最核心、最常用的一段旅程。 即使在 Spring Boot 已成为主流的今天这些底层的概念和配置思想依然是理解 Spring 生态、排查复杂问题、成为一名优秀 Java 开发者的基石。 希望这份笔记能帮助正在学习 Spring 的你理清思路夯实基础。学习是一个持续迭代的过程与君共勉。 Spring 核心部分完结撒花 对应代码仓库https://gitee.com/zzzfatfish/spring-test ✍ 作者fatfish 状态学习中内容持续补充完善… 参考
【狂神说Java】Spring5最新完整教程IDEA版通俗易懂 Spring学习目录(6天) - subeiLY - 博客园