内聚: 内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。 耦合: 耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 划分模块的一个准则就是高内聚低耦合。 2、Spring 相关 full-stack级别的轻量级开源框架,Spring的IOC容器是个Map结构 控制反转(Inversion of Control),简称IOC,把创建对象的权利交给框架,它包括依赖注入(Dependency Injection)和依赖查找(Dependency Lookup),目的就是为了削减计算机程序间的耦合(解除代码中的依赖关系)。
(1)BeanFactory 和 ApplicationContext 的区别 BeanFactory 才是 Spring 容器中的顶层接口。 ApplicationContext 是它的子接口。 BeanFactory 和 ApplicationContext 的区别: 创建对象的时间点不一样。 ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。 BeanFactory:什么使用什么时候创建对象。
(2)ApplicationContext 接口的实现类 ClassPathXmlApplicationContext: 它是从类的根路径下加载配置文件 推荐使用这种 FileSystemXmlApplicationContext: 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。 AnnotationConfigApplicationContext: 当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
(3)要用到的包 maven配置如下,后面的依赖包会自动加载进来
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency>作用: 用于配置对象让 spring 来创建的。 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。 属性: id:给对象在容器中提供一个唯一标识。用于获取对象。 class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。 scope:指定对象的作用范围。 * singleton : 默认值,单例的. * prototype : 多例的. * request : WEB 项目中,Spring 创建一个 Bean 的对象, 将对象存入到 request 域中. * session : WEB 项目中,Spring 创建一个 Bean 的对象, 将对象存入到 session 域中. * global session : WEB 项目中,应用在 Portlet 环境。如果没有 Portlet 环境那么globalSession 相当于 session. init-method:指定类中的初始化方法名称。 destroy-method:指定类中销毁方法名称。
单例对象:scope=“singleton” 一个应用只有一个对象的实例。它的作用范围就是整个引用。 生命周期: 对象出生:当应用加载,创建容器时,对象就被创建了。 对象活着:只要容器在,对象一直活着。 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。 多例对象:scope=“prototype” 每次访问对象时,都会重新创建对象实例。 生命周期: 对象出生:当使用对象时,创建新的对象实例。 对象活着:只要对象在使用中,就一直活着。 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
这个是需要实例化的类
public class UserServiceImpl implements UserService { @Override public void save() { System.out.println("执行了保存用户方法"); } }第一种:使用默认的无参构造函数
<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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 在默认情况下: 它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。--> <bean id="demo1" class="com.xcm.service.impl.UserServiceImpl"></bean> </beans>第二种方式:spring 管理静态工厂-使用静态工厂的方法创建对象
/** * 模拟一个静态工厂,创建业务层实现类 */ public class StaticFactory { public static UserService createUserService(){ return new UserServiceImpl(); } } <!-- 此种方式是: 使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器 id 属性:指定 bean 的 id,用于从容器中获取 class 属性:指定静态工厂的全限定类名 factory-method 属性:指定生产对象的静态方法 --> <bean id="demo2" class="com.xcm.factory.StaticFactory" factory-method="createUserService"/>第三种方式:spring 管理实例工厂- 使用实例工厂的方法创建对象
/** * 模拟一个实例工厂,创建业务层实现类 * 此工厂创建对象,必须现有工厂实例对象,再调用方法 */ public class InstanceFactory { public UserService createAccountService(){ return new UserServiceImpl(); } } <!-- 此种方式是: 先把工厂的创建交给 spring 来管理。 然后在使用工厂的 bean 来调用里面的方法 factory-bean 属性:用于指定实例工厂 bean 的 id。 factory-method 属性:用于指定实例工厂中创建对象的方法。 --> <bean id="instanceFactory" class="com.xcm.factory.InstanceFactory"></bean> <bean id="demo3" factory-bean="instanceFactory" factory-method="createAccountService"></bean>(1)依赖注入的概念 依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。 我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。ioc 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。 简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
(2) 构造函数注入
public class UserServiceImpl2 implements UserService { private String name; private Integer age; private Date birthday; public UserServiceImpl2(String name, Integer age, Date birthday) { this.name = name; this.age = age; this.birthday = birthday; } @Override public void save() { System.out.println(name + " " + age + " " + birthday); } } <!-- 使用构造函数的方式,给 service 中的属性传值 要求: 类中需要提供一个对应参数列表的构造函数。 涉及的标签: constructor-arg 属性: index:指定参数在构造函数参数列表的索引位置 type:指定参数在构造函数中的数据类型 name:指定参数在构造函数中的名称 用这个找给谁赋值 =======上面三个都是找给谁赋值,下面两个指的是赋什么值的============== value:它能赋的值是基本数据类型和 String 类型 ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean --> <bean id="demo4" class="com.xcm.service.impl.UserServiceImpl2"> <constructor-arg name="name" value="鱼化龙"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="birthday" ref="now"></constructor-arg> </bean> <bean id="now" class="java.util.Date"></bean>(3)set 方法注入
public class UserServiceImpl3 implements UserService { private String name; private Integer age; private Date birthday; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public void save() { System.out.println(name + " " + age + " " + birthday); } } <!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式 涉及的标签: property 属性: name:找的是类中 set 方法后面的部分 ref:给属性赋值是其他 bean 类型的 value:给属性赋值是基本数据类型和 string 类型的 实际开发中,此种方式用的较多。 --> <bean id="demo5" class="com.xcm.service.impl.UserServiceImpl3"> <property name="name" value="鱼化龙"></property> <property name="age" value="18"></property> <property name="birthday" ref="now"></property> </bean> <bean id="now" class="java.util.Date"></bean>(4)使用 p 名称空间注入数据(本质还是调用 set 方法)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- xmlns:p="http://www.springframework.org/schema/p" --> <!-- 此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的 set 方法实现注入功能。--> <bean id="demo6" class="com.xcm.service.impl.UserServiceImpl3" p:name="熊出没" p:age="18" p:birthday-ref="now"/> <bean id="now" class="java.util.Date"></bean> </beans>(5)注入集合属性
import com.xcm.service.UserService; import lombok.Setter; import java.util.*; @Setter public class UserServiceImpl4 implements UserService { private String[] myStrs; private List<String> myList; private Set<String> mySet; private Map<String,String> myMap; private Properties myProps; @Override public void save() { System.out.println(Arrays.toString(myStrs)); System.out.println(myList); System.out.println(mySet); System.out.println(myMap); System.out.println(myProps); } }集合内的数据只需要结构相同,标签可以互换
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 注入集合数据 List 结构的: array,list,set Map 结构的 map,entry,props,prop --> <bean id="demo7" class="com.xcm.service.impl.UserServiceImpl4"> <!-- 在注入集合数据时,只要结构相同,标签可以互换 --> <!-- 给数组注入数据 --> <property name="myStrs"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> <!-- 注入 list 集合数据 --> <property name="myList"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <!-- 注入 set 集合数据 --> <property name="mySet"> <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> <!-- 注入 Map 数据 --> <property name="myMap"> <props> <prop key="testA">aaa</prop> <prop key="testB">bbb</prop> </props> </property> <!-- 注入 properties 数据 --> <property name="myProps"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB"> <value>bbb</value> </entry> </map> </property> </bean> <bean id="now" class="java.util.Date"></bean> </beans>注意: 在基于注解的配置中,我们还要多拷贝一个 aop 的 jar 包。
基于注解整合时,导入约束时需要多导入一个 context 名称空间下的约束。
JdbcTemplate <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 告知 spring 创建容器时要扫描的包 --> <context:component-scan base-package="com.xcm"></context:component-scan> </beans>标签分类
和xml配置文件中bean标签实现的功能一样,相当于: <bean id="" class="">
Ⅰ:@Component 作用: 用于把当前类对象存入Spring容器中。相当于在 xml 中配置一个 bean。 属性: value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。
Ⅱ: @Controller、 @Service、 @Repository 他们三个注解都是@Component的衍生注解,他们的作用及属性都是一模一样的。 他们只不过是提供了更加明确的语义化。 @Controller:一般用于表现层的注解。 @Service:一般用于业务层的注解。 @Repository:一般用于持久层的注解。 细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。
和bean标签中的property标签的作用是一样的,相当于: <property name="" ref=""> <property name="" value="">
Ⅰ:@Autowired 作用: 默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用。 默认状态下,如果IOC容器没有任何bean的类型和要注入的变量类型 匹配,则报错。 默认状态下,如果IOC容器中有多个类型匹配时,需要id加以区别,否 则也报错 出现位置: 可以是在变量上,也可以是在方法上 细节: 在使用注解注入时,set方法不是必须的
Ⅱ: @Qualifier 作用: 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和@Autowire 一起使用;但是给方法参数注入时,可以独立使用。 属性: value:指定 bean 的 id。
Ⅲ: @Resource(这个注解属于J2EE的) 作用: @Resource(这个注解属于J2EE的),默认安照名称进行装配,名称可以通过name属性进行指定, 如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。 Java代码 属性: name:指定 bean 的 id。
以上三个注入都只能注入其他bean类型的数据,而基本类型和String无法使用上述注解实现,另外集合类型的注入只能通过XML来实现
Ⅳ:@Value 作用: 注入基本数据类型和 String 类型数据的 属性: value:用于指定值。他可以使用Spring中的 SpEL(也就是Spring的EL表达式) SpEL的写法:$(表达式)
和bean标签中的scope属性作用是一样的,相当于: <bean id="" class="" scope=""> Ⅰ:@Scope 作用: 指定 bean 的作用范围。 属性: value:指定范围的值。 取值:singleton prototype request session globalsession
和bean标签中使用init-method和destroy-method的作用是一样的,相当于: <bean id="" class="" init-method="" destroy-method="" />
Ⅰ:@PostConstruct 作用: 用于指定初始化方法。 Ⅱ:@PreDestroy 作用: 用于指定初始化方法。
注解的优势:配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。 XML 的优势:修改时,不用改源码。不涉及重新编译和部署。
XML 配置注解配置Bean定义<bean id="" class="…"/>@Component,衍生类@Repository、@Service、@ControllerBean名称通过 id 或者 name 指定@Component(“person”)Bean注入<property>或者通过 p 命名空间@Autowired 按类型注入@Qualifier 按名称注入Bean生命过程、作用范围init-method、destroy-method 范围 scope 属性@PostConstruct 初始化 @PreDestroy 销毁 @Score 设置作用范围哦适合场景Bean来自第三方,使用其他Bean的实现类由自己开发基于注解的 spring IoC 配置中,bean 对象的特点和基于 XML 配置是一模一样的。
@Configuration 作用:指定当前类是一个配置类 细节:当配置类作为AnnotationConfigApplicationContext对象创 建的参数时,该注解可以不写。
@ComponentScan 作用:用于通过注解指定spring在创建容器时要扫描的包 属性:value和basePackages的作用是一样的,都是用于指定创 建容器时要扫描的包。 我们使用此注解就等同于在xml中配置了: <context:component-scan base-package=“com.xcm”/>
@Bean 作用:用于把当前方法的返回值作为bean对象存入spring的ioc 容器中 属性:name用于指定bean的id。当不写时,默认值是当前方法 的名称 细节:当我们使用注解配置方法时,如果方法有参数,spring框 架会去容器中查找有没有可用的bean对象。 查找的方式和Autowired注解的作用是一样的
@ Import 作用:用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。 属性: value用于指定其他配置类的字节码。 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
@PropertySource 作用:用于指定properties文件的位置 属性: value指定文件的名称和路径。 关键字:classpath,表示类路径下
@PreDestroy 作用: 用于指定销毁方法。
第一步:Maven 配置
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!-- 数据库 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.6</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.4</version> </dependency> <!-- 测试 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> </dependency> </dependencies>第二步:使用@RunWith 注解替换原有运行器
第三步:使用@ContextConfiguration 指定 spring 配置文件的位置
(1)配置类 主配置类
@ComponentScan(basePackages = {"com.xcm.dao"}) @Import({JdbcConfig.class}) public class SpringConfiguration { }数据源配置
@Configuration @PropertySource("classpath:db.properties") public class JdbcConfig { @Value("${driver}") private String driver; @Value("${url}") private String url; @Value("${user}") private String username; @Value("${password}") private String password; /** * 创建一个数据源,并存入Spring容器中 * @return */ @Bean(name = "dataSource") public DataSource createDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean("runner") public QueryRunner createQueryRunner(DataSource dataSource){ return new QueryRunner(dataSource); } }db.properties 配置文件
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&userSSL=false&serverTimezone=GMT%2B8 user=root password=(2)DAO 编写
@Repository public class UserDao { @Autowired private QueryRunner runner; public void findAll() throws SQLException { List<User> users = runner.query("select * from user", new BeanListHandler<>(User.class)); System.out.println(users); } }(3)测试类编写
import com.xcm.config.SpringConfiguration; import com.xcm.dao.UserDao; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.sql.SQLException; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfiguration.class}) public class SpringTest { @Autowired ApplicationContext ac; @Test public void test1() throws SQLException { //ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class); UserDao userDao = ac.getBean("userDao", UserDao.class); userDao.findAll(); } }简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
作用: 在程序运行期间,不修改源码对已有方法进行增强。 优势: 减少重复代码 提高开发效率 维护方便
AOP 的实现方式: 使用动态代理技术
动态代理的特点: 字节码随用随创建,随用随加载。 它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。 装饰者模式就是静态代理的一种体现。
动态代理常用的有两种方式: (1)基于接口的动态代理 提供者:JDK 官方的 Proxy 类。 要求:被代理类最少实现一个接口。 (2)基于子类的动态代理 提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。 要求:被代理类不能用 final 修饰的类(最终类)。 参考我的另一篇博客:点击这里
(1)AOP 相关术语 Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的 连接点。 Pointcut(切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。 Advice(通知/增强): 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。 Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。 Target(目标对象): 代理的目标对象。 Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。 Proxy(代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。 Aspect(切面): 是切入点和通知(引介)的结合。
(2)关于代理的选择 在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
(1)准备工作: maven 配置
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.5.4</version> </dependency>(2)配置步骤:
第一步:把通知类用 bean 标签配置起来
<!-- 配置通知 --> <bean id="txManager" class="com.xcm.utils.TransactionManager"> <property name="dbAssit" ref="dbAssit"></property> </bean>第二步:使用 aop:config 声明 aop 配置
aop:config: 作用:用于声明开始 aop 的配置 <aop:config> <!-- 配置的代码都写在此处 --> </aop:config>第三步:使用 aop:aspect 配置切面
aop:aspect: 作用: 用于配置切面。 属性: id:给切面提供一个唯一标识。 ref:引用配置好的通知类 bean 的 id。 <aop:aspect id="txAdvice" ref="txManager"> <!--配置通知的类型要写在此处--> </aop:aspect>第四步:使用 aop:pointcut 配置切入点表达式
aop:pointcut: 作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。 属性: expression:用于定义切入点表达式。 id:用于给切入点表达式提供一个唯一标识 <aop:pointcut expression="execution( public void com.xcm.service.impl.AccountServiceImpl.transfer( java.lang.String, java.lang.String, java.lang.Float) )" id="pt1"/>第五步:使用 aop:xxx 配置对应的通知类型
aop:before 作用: 用于配置前置通知。指定增强的方法在切入点方法之前执行 属性: method:用于指定通知类中的增强方法名称 ponitcut-ref:用于指定切入点的表达式的引用 poinitcut:用于指定切入点表达式 执行时间点: 切入点方法执行之前执行 <aop:before method="beginTransaction" pointcut-ref="pt1"/> aop:after-returning 作用: 用于配置后置通知 属性: method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 切入点方法正常执行之后。它和异常通知只能有一个执行 <aop:after-returning method="commit" pointcut-ref="pt1"/> aop:after-throwing 作用: 用于配置异常通知 属性: method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 切入点方法执行产生异常后执行。它和后置通知只能执行一个 <aop:after-throwing method="rollback" pointcut-ref="pt1"/> aop:after 作用: 用于配置最终通知 属性: method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 无论切入点方法执行时是否有异常,它都会在其后面执行。 <aop:after method="release" pointcut-ref="pt1"/>(1)测试类
@Service public class UserServiceImpl implements UserService { @Override public void save() { // int i = 1/0; System.out.println("执行了用户保存操作"); } }(2)通知类
import org.aspectj.lang.ProceedingJoinPoint; public class Logger { public void beforeLog(){ System.out.println("前置"); } public void afterReturningLog(){ System.out.println("后置"); } public void afterThrowingLog(){ System.out.println("异常"); } public void afterLog(){ System.out.println("最终"); } public Object aroundLog(ProceedingJoinPoint pjp){ System.out.println("开始环绕通知"); Object rtValue = null; try { Object[] args = pjp.getArgs(); System.out.println("前置通知"); rtValue = pjp.proceed(args); System.out.println("后置通知"); } catch (Throwable throwable) { System.out.println("异常通知"); throwable.printStackTrace(); } finally { System.out.println("最终通知"); } return rtValue; } }(3)bean.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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.xcm.service"/> <!-- 配置通知类 --> <bean id="loggerManager" class="com.xcm.utils.Logger"></bean> <!-- 声明 aop 配置 --> <aop:config> <!-- 配置切入点 --> <aop:pointcut id="pc1" expression="execution(* com.xcm.service.impl.*.*(..))"></aop:pointcut> <aop:pointcut id="pc2" expression="execution(* com.xcm.service.impl.*.find*(..))"></aop:pointcut> <aop:aspect id="logAdvice" ref="loggerManager"> <!-- 前置通知 --> <!--<aop:before method="beforeLog" pointcut-ref="pc1"/>--> <!-- 后置通知 --> <!--<aop:after-returning method="afterReturningLog" pointcut-ref="pc1"/>--> <!-- 异常通知 --> <!--<aop:after-throwing method="afterThrowingLog" pointcut-ref="pc1"/>--> <!-- 最终通知 --> <!--<aop:after method="afterLog" pointcut-ref="pc1"/>--> <!-- 环绕通知 --> <aop:around method="aroundLog" pointcut-ref="pc1"/> </aop:aspect> </aop:config> </beans>(4)测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:bean.xml"}) public class BeanTest { @Resource private UserService userService; @Test public void test1(){ userService.save(); } }(1)测试类
@Service public class UserServiceImpl implements UserService { @Override public void findAll() { System.out.println("执行了查询所有用户操作"); } @Override public void save() { int i = 1/0; System.out.println("执行了用户保存操作"); } }(2)配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 告知 spring,在创建容器时要扫描的包 --> <context:component-scan base-package="com.xcm"/> <!-- 开启 spring 对注解 AOP 的支持 --> <aop:aspectj-autoproxy/> </beans>(2)通知类
@Component("logManager") @Aspect // //表明当前类是一个切面类 public class Logger { /** * @Before * 作用: * 把当前方法看成是前置通知。 * 属性: * value:用于指定切入点表达式,还可以指定切入点表达式的引用。 */ @Before("execution(* com.xcm.service.impl.*.*(..))") public void beforeLog(){ System.out.println("前置"); } /** * @AfterReturning * 作用: * 把当前方法看成是后置通知。 * 属性: * value:用于指定切入点表达式,还可以指定切入点表达式的引用 */ @AfterReturning("execution(* com.xcm.service.impl.*.*(..))") public void afterReturningLog(){ System.out.println("后置"); } /** * @AfterThrowing * 作用: * 把当前方法看成是异常通知。 * 属性: * value:用于指定切入点表达式,还可以指定切入点表达式的引用 */ @AfterThrowing("execution(* com.xcm.service.impl.*.*(..))") public void afterThrowingLog(){ System.out.println("异常"); } /** * @After * 作用: * 把当前方法看成是最终通知。 * 属性: * value:用于指定切入点表达式,还可以指定切入点表达式的引用 */ @After("execution(* com.xcm.service.impl.*.*(..))") public void afterLog(){ System.out.println("最终"); } }环绕通知
@Component("logManager") @Aspect // //表明当前类是一个切面类 public class Logger { /** * @Pointcut * 作用: * 指定切入点表达式 * 属性: * value:指定表达式的内容 */ @Pointcut("execution(* com.xcm.service.impl.*.*(..))") private void pc1(){} /** *@Around * 作用: * 把当前方法看成是环绕通知。 * 属性: * value:用于指定切入点表达式,还可以指定切入点表达式的引用。 */ @Around("pc1()") //注意:千万别忘了写括号 public Object aroundLog(ProceedingJoinPoint pjp){ System.out.println("开始环绕通知"); Object rtValue = null; try { Object[] args = pjp.getArgs(); System.out.println("前置通知"); rtValue = pjp.proceed(args); System.out.println("后置通知"); } catch (Throwable throwable) { System.out.println("异常通知"); throwable.printStackTrace(); } finally { System.out.println("最终通知"); } return rtValue; } }(3)如果不写 XML 文件
@Configuration @ComponentScan(basePackages = {"com.xcm"}) @EnableAspectJAutoProxy public class SpringConfiguration { }它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多 的操作模板类。 操作关系型数据的: JdbcTemplate HibernateTemplate 操作 nosql 数据库的: RedisTemplate 操作消息队列的: JmsTemplate 在导包的时候,除了要导入这个 jar 包 外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。
我们可以参考它的源码:
public JdbcTemplate() { } public JdbcTemplate(DataSource dataSource) { setDataSource(dataSource); afterPropertiesSet(); } public JdbcTemplate(DataSource dataSource, boolean lazyInit) { setDataSource(dataSource); setLazyInit(lazyInit); afterPropertiesSet(); }除了默认构造函数之外,都需要提供一个数据源。既然有set方法,依据我们之前学过的依赖注入,我们可以在配置文件中配置这些对象。
文件配置
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置一个数据库的操作模板:JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql:///spring_day02"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property> </bean> </beans>基本使用
public class JdbcTemplateDemo2 { public static void main(String[] args) { //1.获取 Spring 容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据 id 获取 bean 对象 JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate"); //3.执行操作 jt.execute("insert into account(name,money)values('eee',500)"); } }更新操作
jt.update("insert into account(name,money)values(?,?)","fff",5000);查询操作
public class JdbcTemplateDemo3 { public static void main(String[] args) { //1.获取 Spring 容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据 id 获取 bean 对象 JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate"); //3.执行操作 //查询所有 List<Account> accounts = jt.query("select * from account where money > ? ", new AccountRowMapper(), 500); for(Account o : accounts){ System.out.println(o); } } } public class AccountRowMapper implements RowMapper<Account>{ @Override public Account mapRow(ResultSet rs, int rowNum) throws SQLException { Account account = new Account(); account.setId(rs.getInt("id")); account.setName(rs.getString("name")); account.setMoney(rs.getFloat("money")); return account; } }(1)第一种方式
public class AccountDaoImpl implements IAccountDao { private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void updateAccount(Account account) { jdbcTemplate.update("update account set money = ? where id = ? ",account.getMoney(),account.getId()); } }创建 Dao 时需要注入 jdbcTemplate
(2)第二种方式:让 dao 继承 JdbcDaoSupport JdbcDaoSupport 是 spring 框架为我们提供的一个类,该类中定义了一个 JdbcTemplate 对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源:具体源码如下:
public abstract class JdbcDaoSupport extends DaoSupport { //定义对象 private JdbcTemplate jdbcTemplate; //set 方法注入数据源,判断是否注入了,注入了就创建 JdbcTemplate public final void setDataSource(DataSource dataSource) { //如果提供了数据源就创建 JdbcTemplate if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource(){ this.jdbcTemplate = createJdbcTemplate(dataSource); initTemplateConfig(); } } //使用数据源创建 JdcbTemplate protected JdbcTemplate createJdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } //当然,我们也可以通过注入 JdbcTemplate 对象 public final void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; initTemplateConfig(); } //使用 getJdbcTmeplate 方法获取操作模板对象 public final JdbcTemplate getJdbcTemplate() { return this.jdbcTemplate; } }创建自己的实现类
/** * 账户的持久层实现类 * 此版本 dao,只需要给它的父类注入一个数据源 */ public class AccountDaoImpl2 extends JdbcDaoSupport implements IAccountDao { @Override public Account findAccountById(Integer id) { //getJdbcTemplate()方法是从父类上继承下来的。 List<Account> list = getJdbcTemplate().query("select * from account where id = ? ",new AccountRowMapper(),id); return list.isEmpty()?null:list.get(0); } }创建 Dao 时需要注入 dataSource
(3)两种方法的区别 第一种在 Dao 类中定义 JdbcTemplate 的方式,适用于所有配置方式(xml 和注解都可以)。 第二种让 Dao 继承 JdbcDaoSupport 的方式,只能用于基于 XML 的方式,注解用不了。
第一:JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方案。 第二:spring 框架为我们提供了一组事务控制的接口。 第三:spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。
(1)PlatformTransactionManager
此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法,如下图: 我们在开发中都是使用它的实现类,如下图: 真正管理事务的对象 org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 SpringJDBC 或 iBatis 进行持久化数据时使用 org.springframework.orm.hibernate5.HibernateTransactionManager 使用 Hibernate 版本进行持久化数据时使用
(2)TransactionDefinition 它是事务的定义信息对象,里面有如下方法:
本案例是模拟银行转账
(1)编写 POJO
@Data public class Account { private Long id; private Long uid; private Double money; }(2)编写 Dao 层
public interface AccountDao { // 查询所有用户 List<Account> findAllAccount(); // 根据id查询用户 Account findAccountById(Long id); // 更新账户 int updateAccount(Long id, Double money); } public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public List<Account> findAllAccount() { List<Account> accounts = getJdbcTemplate().query("select * from account", new AccountRowMapper()); return accounts; } @Override public Account findAccountById(Long id) { List<Account> accounts = getJdbcTemplate().query("select * from account where id = ?", new AccountRowMapper(), id); if (accounts.size() > 1) { throw new RuntimeException("存在两个相同的账户"); } return accounts.isEmpty() ? null : accounts.get(0); } @Override public int updateAccount(Long id, Double money) { int i = getJdbcTemplate().update("update account set money = ? where id = ?", money, id); return i; } } public class AccountRowMapper implements RowMapper<Account> { @Override public Account mapRow(ResultSet rs, int i) throws SQLException { Account account = new Account(); account.setId(rs.getLong("id")); account.setUid(rs.getLong("uid")); account.setMoney(rs.getDouble("money")); return account; } }(3)编写 Service 层
public interface AccountService { // 查询所有账户 List<Account> findAllAccount(); // 转账操作 void transferAccount(Long fromId, Long toId, Double money); } public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public List<Account> findAllAccount() { return accountDao.findAllAccount(); } @Override public void transferAccount(Long fromId, Long toId, Double money) { Account from = accountDao.findAccountById(fromId); Account to = accountDao.findAccountById(toId); accountDao.updateAccount(fromId, from.getMoney() - money); // 模拟转账发生异常 int i = 1/0; accountDao.updateAccount(toId, to.getMoney() + money); } }(4)XML 配置 bean.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" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://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 http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置 service --> <bean id="accountService" class="com.xcm.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean> <!-- 配置 dao --> <bean id="accountDao" class="com.xcm.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 引入数据库配置文件 --> <context:property-placeholder ignore-unresolvable="true" location="classpath*:db.properties"/> <!-- 配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- ===============配置事务============== --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事务的通知引用事务管理器 --> <tx:advice id="txAdvice"> <!--配置事务的属性--> <tx:attributes> <!-- 指定方法名称:是业务核心方法 read-only:是否是只读事务。默认 false,不只读。 isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。 propagation:指定事务的传播行为。 timeout:指定超时时间。默认值为:-1。永不超时。 rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。 没有默认值,任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回 滚。没有默认值,任何异常都回滚。 --> <tx:method name="*" read-only="false" propagation="REQUIRED"/> <tx:method name="find*" read-only="true" propagation="SUPPORTS"/> </tx:attributes> </tx:advice> <!-- 配置 AOP --> <aop:config> <!-- 配置 AOP 切入点表达式 --> <aop:pointcut id="pt1" expression="execution(* com.xcm.service.impl.*.*(..))"/> <!-- 第五步:配置切入点表达式和事务通知的对应关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/> </aop:config> </beans>db.properties
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&userSSL=false&serverTimezone=GMT%2B8 jdbc.username=root jdbc.password=(5)测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:bean.xml"}) public class BeanTest { @Resource AccountService accountService; @Test public void testFindAllAccount(){ List<Account> allAccount = accountService.findAllAccount(); for(Account account : allAccount) { System.out.println(account); } } @Test public void testTranfer(){ accountService.transferAccount(3L, 1L, 100.00); System.out.println("转账成功"); } }(1)配置步骤
第一步:配置事务管理器并注入数据源
<!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>第二步:在业务层使用@Transactional 注解 该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。 出现接口上,表示该接口的所有实现类都有事务支持。 出现在类上,表示类中所有方法有事务支持 出现在方法上,表示方法有事务支持。 以上三个位置的优先级:方法>类>接口
第三步:在配置文件中开启 spring 对注解事务的支持
<!-- 开启 spring 对注解事务的支持 --> <tx:annotation-driven transaction-manager="transactionManager"/>(2)使用纯注解的方式 ① POJO和前面一样
② Dao 层编写
@Repository public class AccountDaoImpl implements AccountDao { @Resource private JdbcTemplate jdbcTemplate; @Override public List<Account> findAllAccount() { List<Account> accounts = jdbcTemplate.query("select * from account", new AccountRowMapper()); return accounts; } @Override public Account findAccountById(Long id) { List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new AccountRowMapper(), id); if (accounts.size() > 1) { throw new RuntimeException("存在两个相同的账户"); } return accounts.isEmpty() ? null : accounts.get(0); } @Override public int updateAccount(Long id, Double money) { int i = jdbcTemplate.update("update account set money = ? where id = ?", money, id); return i; } }③ Service 层编写
@Service @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public class AccountServiceImpl implements AccountService { @Resource private AccountDao accountDao; @Override public List<Account> findAllAccount() { return accountDao.findAllAccount(); } @Override @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public void transferAccount(Long fromId, Long toId, Double money) { Account from = accountDao.findAccountById(fromId); Account to = accountDao.findAccountById(toId); accountDao.updateAccount(fromId, from.getMoney() - money); //int i = 1/0; accountDao.updateAccount(toId, to.getMoney() + money); } }④ 配置类编写
@Configuration @ComponentScan({"com.xcm"}) @Import({JdbcConfiguration.class}) @EnableTransactionManagement // 对事物的支持 public class SpringConfiguration { } @Configuration @PropertySource("classpath:db.properties") public class JdbcConfiguration { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 配置数据源 * @return */ @Bean(name = "dataSource") public DataSource createDataSource(){ DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } /** * 创建 * @param dataSource * @return */ @Bean public JdbcTemplate createJdbcTemplate(DataSource dataSource){ return new JdbcTemplate(dataSource); } /** * 配置事务管理器 */ @Bean public TransactionManager createTransactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }⑤ 测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfiguration.class}) public class BeanTest2 { @Resource AccountService accountService; @Test public void testFindAllAccount(){ List<Account> allAccount = accountService.findAllAccount(); for(Account account : allAccount) { System.out.println(account); } } @Test public void testTranfer(){ accountService.transferAccount(3L, 1L, 100.00); System.out.println("转账成功"); } }