spring
- 概念
- spring框架的组成
- spring环境搭建
- 依赖
- 创建spring配置文件
- srping工厂编码
- 定义Bean类型
- spring-context.xml中的< beans >内配置bean标签
- 调用spring工厂的API(ApplicationContext接口)
- 依赖与配置文件
- spring依赖关系
- IOC(inversion of Control) 控制反转
- ioc优点
- 项目中的强耦合问题
- DI(dependency injection)依赖注入
- 概念
- < bean>元素的常用属性及其子元素
- id
- class
- scope
- set注入
- 基本类型+字符串类型+日期类型
- 集合类型
- 数组
- list
- set
- map
- Properties
- 空指针null注入
- 自建类型
- 构造注入
- 定义目标Bean类型
- 注入
- 自动注入
- 拓展注入
- Bean的一些细节
- Bean 的作用域
- 控制简单对象的单列,多例模式
- FactoryBean创建复杂对象
- 实现FactoryBean接口
- 配置与获取
- Spring配置
- 别名
- Bean的配置
- import
- spring工厂特性
- 生命周期
- 声明周期阶段
- 单利bean:singleton
- 多例bean:prototype
- 代理设计模式
- 概念
- 静态代理设计模式
- 静态代理的问题
- 动态代理设计模式
- JDK动态代理实现(基于接口)
- 定义核心业务
- 定义被代理者
- 定义InvocationHandler的实现类
- 测试
- 优化
- 测试
- CGlib动态代理实现(基于继承)
- 导包
- 定义MethodInterceptor
- 测试
- 优化
- AOP
- 作用
- 依赖
- spring-context.xml引入AOP命名空间
- < aop:config> 元素及其子元素
- 定义通知类(添加额外功能)
- 定义bean标签
- 定义切入点,形成切面
- 通知类
- MethodInterceptor
- AfterReturningAdvice
- ThrowsAdvice
- MethodInterceptor
- 通配切入点
- AOP小结
- JDK和CGLIB选择
- 后处理器
- 整合mybatis
- pom.xml
- 引入jdbc.properties配置文件
- 整合spring配置文件和properties配置文件
- Druid连接池可选参数
- 导入依赖
- 配置SqlSessionFactory
- 配置MapperScannerConfigurer
- 配置service
- 事务
- 配置DataSourceTransactionManager
- 配置事务通知
- 事务属性
- 隔离级别
- 特性
- 并发问题
- 传播行为
- 读写性
- 事务超时
- 事务回滚
- 编织
- 注解开发
- 配置注解的支持
- 定义要扫描的包
- 声明bean
- @Component
- @Service
- @Scope
- 注入(DI)
- @Resource
- @Autowired
- @Value
- 区别:
- JavaConfig
- entity
- AppConfig
- 事务控制
- 注解所需配置
- aop
- 使用java的方式来配置spring
- spring集成JUnit
- 导入依赖
- 编码
概念
spring是一个项目管理框架,同时也是java EE解决方案
spring是众多优秀设计模式的组合(工厂,单列,代理,适配器,包装器,观察者,模板,则略)
spring并未代替现有的框架产品,而是将众多框架进行有机
spring框架的组成
spring框架由诸多模块组成,可分类为
核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,spEL,AOP
测试:模拟对象,TestContext框架,springMVC,webTestClient
数据访问:事务,DAO支持,JDBC,ORM
Spring MVC 和spring WebFlux web框架
继承:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
语言:kotlin,GROOVY,动态语言
spring环境搭建
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
创建spring配置文件
初步测试
spring-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
完整版
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
</beans>
srping工厂编码
定义Bean类型
package com.blb.dao;
public class UserDaoImpl implements UserDao{
public void deleteUser(Integer id) {
System.out.println("删除用户");
}
}
package com.blb.service;
public class UserServiceImpl implements UserService{
public void deleteUser(Integer id) {
System.out.println("删除用户");
}
}
spring-context.xml中的< beans >内配置bean标签
配置实例 (id ="唯一标识" class="需要被创建的目标对象全限定名")
<?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">
<!-- 要生产的对象-->
<bean id="userDao" class="com.blb.dao.UserDaoImpl"></bean>
<bean id="userService" class="com.blb.service.UserServiceImpl"></bean>
</beans>
调用spring工厂的API(ApplicationContext接口)
//读取配置文件中所需要创建的bean对象,并获得工厂对象
ApplicationContext context=new ClassPathXmlApplicationContext("/spring-context.xml");
//通过id获取bean对象
UserDao userDao=(UserDao)context.getBean("userDao");
UserService userService = (UserService)context.getBean("userService");
userDao.deleteUser(1);
userService.deleteUser(1);
依赖与配置文件
spring框架包含多个模块,每个模块各司其职,可结合需求引入相关依赖jar包实现功能
spring依赖关系
IOC(inversion of Control) 控制反转
inverse Of controll :控制反转
反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送(主动变为被动,即反转)解决了具有依赖关系的组件之间的强耦合,使得项目型态更加稳健
ioc优点
解耦,是降低程序耦合度,也就是减少程序代码之间的依赖性,如果代码之间的依赖性很高,修改一处代码会影响很多其他的代码,这就给项目的稳定性带来的问题,不利于代码的扩展和维护。
没有IOC的程序中,我们使用new来完成对象的创建,如果需要的对象的类型发生改变,就需要手动修改代码。
有了IOC后,对象的创建由第三方(Spring容器)完成,由Spring来管理应用中所有对象的生命周期,开发者只需要关注自己的业务逻辑,代码更利于扩展和维护
项目中的强耦合问题
package com.blb.service;
import com.blb.dao.UserDao;
import com.blb.dao.UserDaoImpl;
public class UserServiceImpl implements UserService{
//满足依赖关系强耦合
//强耦合了UserDaoImpl,使得UserServiceImpl变的不稳健了
private UserDao userDao=new UserDaoImpl();
public void deleteUser(Integer id) {
userDao.deleteUser(1);
}
}
解决方案
package com.blb.service;
import com.blb.dao.UserDao;
import com.blb.dao.UserDaoImpl;
public class UserServiceImpl implements UserService{
//满足依赖关系强耦合
//不引用任何一个具体的主键(实现类)在需要其他主键的位置预留存取值入口
private UserDao userDao;
//给userDao定义get和set方法允许userDao接收spring赋值
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void deleteUser(Integer id) {
userDao.deleteUser(1);
}
}
<!-- 要生产的对象-->
<bean id="userDao" class="com.blb.dao.UserDaoImpl"></bean>
<bean id="userService" class="com.blb.service.UserServiceImpl">
<!-- 由spring为userDao属性赋值,ref是id为userDao的一个bean-->
<property name="userDao" ref="userDao"></property>
</bean>
此时,如果需要更换其他UserDAO,则UserServiceImpl不用任何改动,只需要换一下里面的配置,则此时的UserServicImpl组件变得更加稳健
DI(dependency injection)依赖注入
概念
在spring创建对象的同时,为其属性赋值,称之为依赖注入
- 依赖注入(DI): Dependency Injection
- 把有依赖关系的类放到容器中,解析出这些类的实例,就是依赖注入。
- 依赖注入是实现IOC的一种方式。
- IoC可以认为是一种全新的设计模式,但是理论和时间成熟相对较晚,并没有包含在GoF(23种设计模式)中
< bean>元素的常用属性及其子元素
id
bean的唯一标识,同一个Spring容器中不允许重复
class
全类名,用于反射创建对象
scope
scope主要有两个值:singleton和prototype
如果设置为singleton则一个容器中只会有这个一个bean对象。默认容器创建的时候就会创建该对象。
如果设置为prototype则一个容器中会有多个该bean对象。每次调用getBean方法获取时都会创建一个新对象
属性或子元素名称 | 描述 |
id | Bean在BeanFactory中的唯一标识,在代码中通过BeanFactory获取Bean实例时需要以此作为索引名称 |
class | Bean的具体实现,使用类的完整限定类名 |
scope | 制定Bean实例的作用域 |
< constructor-arg > | 子元素,使用构造方法注入,指定构造方法的参数,该元素的index属性指定参数的序号,value属性指定Bean的属性值,ref属性指定对 BeanFactory中的其他Bean的引用关系,type属性指定参数类型,value指定参数的常量值 |
< list > | < property > 元素的子元素,用于封装List或者数组类型依赖注入 |
< map > | < property > 元素的子元素,用于封装Map类型依赖注入 |
< SET > | < property > 元素的子元素,用于封装Set类型依赖注入 |
< entry > | < map > 元素的子元素,用于设置一个键值对 |
set注入
默认是通过set方法来实现的所以如果发现提示报红,注意检查下是否添加了set方法
public class User {
private Integer id;
private String password;
private String sex;
private Integer age;
private Date bornDate;
private String[] hobbys;
private List<String> names;
private Set<String> phones;
private Map<String,String> countries;
private Properties files;
private address address;
Getters and Setters
}
基本类型+字符串类型+日期类型
<!--
name属性用来指定要设置哪个属性
value属性用来设置要设置的值
ref属性用来给引用类型的属性设置值,可以写上Spring容器中bean的id
-->
<bean id="user" class="com.blb.entity.User">
<!-- 简单基本类型 包括string 和 Date-->
<property name="id" value="1"></property>
<property name="password" value="123456"></property>
<property name="sex" value="男"></property>
<property name="age" value="18"></property>
<property name="bornDate" value="2021/3/28 12:00:00"></property>
</bean>
注意日期的格式是用"/"来分隔,spring会自动转化为日期类型
集合类型
数组
<!-- 数组-->
<property name="hobbys">
<array>
<value>football</value>
<value>basketball</value>
</array>
</property>
list
<!-- list-->
<property name="names">
<list>
<value>远科</value>
<value>小科</value>
</list>
</property>
set
<!-- set -->
<property name="phones">
<set>
<value>110</value>
<value>120</value>
</set>
</property>
map
<!-- map -->
<property name="countries">
<map>
<entry key="zh" value="china"></entry>
<entry key="en" value="english"></entry>
</map>
</property>
Properties
<property name="files" >
<props>
<prop key="url">jdbc:mysql:xxx</prop>
<prop key="username">root</prop>
</props>
</property>
空指针null注入
<!--空指针null注入 -->
<property name="xxx">
<null></null>
</property>
自建类型
public class User {
private address address;
}
//次要bean,被作为属性
<bean id="addr" class="com.blb.entity.address">
<property name="city" value="wh"></property>
<property name="id" value="1"></property>
</bean>
<!--ref引用spring中已经创建很好的对象-->
<!--value是一个具体的值,基本数据类型-->
//主要bean,操作的主体
<bean id="user" class="com.blb.entity.User">
<property name="address" ref="addr"></property>
</bean>
构造注入
创建对象时,spring工厂会通过构造方法为对象的属性赋值
定义目标Bean类型
package com.blb.entity;
public class student {
private int id;
private String name;
private String sex;
private Integer age;
public student(int id, String name, String sex, Integer age) {
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
@Override
public String toString() {
return "student{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
注入
<!-- 构造注入-->
<!-- 参数名是name,则有name="构造方法的形参名" -->
<bean id="student" class="com.blb.entity.student">
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg name="sex" value="male"></constructor-arg>
<constructor-arg name="name" value="dyk"></constructor-arg>
</bean>
<!-- 构造注入-->
<!--根据参数顺序注入 -->
<bean id="student" class="com.blb.entity.student">
<constructor-arg index="0" value="18"></constructor-arg>
<constructor-arg index="1" value="1"></constructor-arg>
<constructor-arg index="2" value="male"></constructor-arg>
<constructor-arg index="3" value="dyk"></constructor-arg>
</bean>
自动注入
不用在配置中,指定为那个属性赋值,及赋什么值
由spring自动根据某个原则,在工厂中找一个bean,为属性注入属性值
1. byType自动装配:byType会自动查找,和自己对象set方法参数的类型相同的bean
保证所有的class唯一(类为全局唯一)
2. byName自动装配:byName会自动查找,和自己对象set对应的值对应的id
保证所有id唯一,并且和set注入的值一致
<!-- 要生产的对象-->
<bean id="userDao" class="com.blb.dao.UserDaoImpl"></bean>
<!- 为UserServiceImpl中的属性基于属性名称自动注入-->
<bean id="userService" class="com.blb.service.UserServiceImpl" autowire="byName">
</bean>
<!-- 要生产的对象-->
<bean id="userDao" class="com.blb.dao.UserDaoImpl"></bean>
<!- 为UserServiceImpl中的属性基于类型自动注入-->
<bean id="userService" class="com.blb.service.UserServiceImpl" autowire="byType">
</bean>
如果以属性名自动填充那么就要id和属性名一样,如果以属性类型自动填充,那么同一个类型不能由多个
小结:
- byName的时候需保证需要注入的bean的id唯一,并且需要匹配的id值必须跟需要注入的属性名一致。
- byType的时候需保证需要注入的bean的class唯一,并且这个bean的类型跟需要注入的属性类型一致。
- 所有bean的autowire属性默认为no,但是可以在标签下使用
default-autowire="XX"
来修改整个配置文件的默认自动装备的值。
拓展注入
使用p和c命名空间需要导入xml约束
xmlns:p=“http://www.springframework.org/schema/p”
xmlns:c=“http://www.springframework.org/schema/c”
?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--p命名空间注入/set注入,可以直接注入属性的值-》property-->
<bean id="user" class="pojo.User" p:name="cxk" p:id="20" >
</bean>
<!--c命名空间,通过构造器注入,需要写入有参和无参构造方法-》construct-args-->
<bean id="user2" class="pojo.User" c:name="cbh" c:id="22"></bean>
</beans>
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("user",User.class);
System.out.println(student);
总结:
p命名名
- 需要引入P的命名空间
- 是通过set方式注入的,需要确保有setter方法
c命名
- 需要导入C命名空间
- 是通过构造器方式注入,需要确保有对应合法参数列表的构造器
Bean的一些细节
Bean 的作用域
作用域 | 描述 |
singleton | 默认的作用域,使用singleton定义的Bean在spring容器中只有一个Bean实例 |
prototype | spring 容器每次获取prototype定义的Bean,容器都将创建一个新的Bean实例 |
request | 在一次HTTP请求中容器将返回一个Bean实例,不同的HTTP请求返回不同的Bean实例,仅在Web Spring应用程序的上下文中使用 |
session | 在以此HTTP Session中,容器将返回同一个Bean实例,仅在Web Spring应用程序的上下文中使用 |
application | 为每个ServletContext对象创建一个实例,即同一个应用共享一个Bean实例,仅在Web Spring应用程序的上下文中使用 |
websocket | 为每个WebSocket对象创建一个Bean实例 仅在Web Spring应用程序的上下文中使用 |
控制简单对象的单列,多例模式
配置<bean scope="singleton|prototype">
singleton(默认) :每次调用工厂,得到的都是同一个对象
prototype: 每次调用工厂,都会创建新对象
<bean id="userDao" class="com.blb.dao.UserDaoImpl" scope="singleton"></bean>
注意:根据场景决定对象的单列,多例模式
可以共用:Service DAO sqlSessionFactory(或者是所有的工厂)
不可共用:Connection SqlSession
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
system.out.println(user == user2)//结果为true
总结:在配置文件加载的时候,容器(< bean>)中管理的对象就已经初始化了
FactoryBean创建复杂对象
让spring可以创建复杂对象,或者无法直接通过反射创建的对象
例如:Connection,SqlSessionFactory
实现FactoryBean接口
package com.blb.factorybean;
import org.springframework.beans.factory.FactoryBean;
import java.sql.Connection;
import java.sql.DriverManager;
public class ConnectionFactoryBean implements FactoryBean<Connection> {
@Override
public Connection getObject() throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection("jdbc:mysql://localhost:3306/db3?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8","root","123456");
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
注意 isSingleton方法的返回值,需要根据所创建对象的特点决定返回true/false
例如Connection不应该被多个用户共享,返回false
例如SqlSessionFactory重量级资源,不该多创建,返回true
配置与获取
<!-- 复杂对象-->
<!--当从工厂对象索要一个bean时,如果是factorybean 实际返回的是工厂Bean的getobject方法的返回值-->
<bean id="conn" class="com.blb.factorybean.ConnectionFactoryBean"></bean>
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
Connection conn = (Connection)context.getBean("conn");
PreparedStatement pst = conn.prepareStatement("select * from t_user");
ResultSet rs = pst.executeQuery();
while(rs.next()){
System.out.println(rs.getInt("id"));
}
Spring配置
别名
<bean id="user" class="pojo.User">
<constructor-arg name="name" value="chen"></constructor-arg>
</bean>
<alias name="user" alias="userLove"/>
<!-- 使用时
User user2 = (User) context.getBean("userLove");
-->
Bean的配置
<!--id:bean的唯一标识符,也就是相当于我们学的对象名
class:bean对象所对应的会限定名:包名+类型
name:也是别名,而且name可以同时取多个别名 多个别名之间可以用空格、逗号、封号隔开 -->
<bean id="user" class="pojo.User" name="u1 u2,u3;u4">
<property name="name" value="chen"/>
</bean>
<!-- 使用时
User user2 = (User) context.getBean("u1");
-->
- d:bean的唯一标识符,相当于我们取的对象名
- class:bean对应的全限定名: 包名+类名
- name:也是别名,而且可以取多个别名,多个别名之间可以用空格、逗号、封号隔开
import
import一般用于团队开发使用,它可以将多个配置文件,导入合并为一个
<import resource="beans.xm1"/>
<import resource="beans2.xml"/>
<import resource="beans3.xm1"/>
使用的时候,直接使用总的配置就可以了
按照在总的xml中的导入顺序来进行创建,后导入的会重写先导入的,最终实例化的对象会是后导入xml中的那个
当有冲突的时候按照顺序后面覆盖前面的原则
spring工厂特性
饿汉式创建优势
工厂创建之后,会将spring配置文件的所有对象都创建完成(饿汉式)
提高程序运行效率,避免多次IO,减少对象创建时间(概念接近连接池,一次性创建好,使用时直接获取)
生命周期
自定义初始化方法:在bean里添加init-method属性=方法名 spring会在创建对象之后,调用次方法
自定义销毁方法 ,在bean里添加destroy-method属性=方法名 spring会在销毁对象之前调用次方法
销毁:工厂的close()方法被调用之后,spring会销毁所有已创建的单利对象
分类:Singleton对象由spring容器销毁,prototype对象由JVM销毁
package com.blb.entity;
public class address {
private Integer id;
private String city;
public address() {
System.out.println("构造方法");
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println("set方法");
this.id = id;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public void init(){
System.out.println("初始化");
}
public void destory()
{
System.out.println("销毁");
}
@Override
public String toString() {
return "address{" +
"id=" + id +
", city='" + city + '\'' +
'}';
}
}
<bean id="addr" class="com.blb.entity.address" init-method="init" destroy-method="destory">
<property name="city" value="wh"></property>
<property name="id" value="1"></property>
</bean>
或者使用注解
@PostConstruct //初始化
public void init(){
System.out.println("初始化");
}
@PreDestroy
public void destory()
{
System.out.println("销毁");
}
声明周期阶段
单利bean:singleton
单例模式下容器只会创建一个实例,每次从容器中获取的都是同一个对象
随工厂启动:创建》构造方法》set方法(注入值)》init(初始化)》构建完成》随工厂关闭销毁
多例bean:prototype
原型模式下每次从容器中获取的都是一个新的对象
被使用时创建》构造方法》set方法(注入值)》init(初始化)》构建完成》JVM垃圾回收销毁
代理设计模式
概念
将核心功能与辅助功能(事务,日志,性能监控代码)分离,达到核心业务功能更纯粹,辅助业务功能服用
静态代理设计模式
通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易替换实现类,利于维护。
package com.blb.service;
public interface FangDongService {
public void zufang();
}
package com.blb.service;
public class FangDongServiceImpl implements FangDongService{
@Override
public void zufang() {
// 核心功能
System.out.println("签合同");
System.out.println("租房");
}
}
package com.blb.service;
public class FangDongProxy implements FangDongService{
private FangDongService fangDongService=new FangDongServiceImpl();
@Override
public void zufang() {
//辅助功能,额外功能
System.out.println("发布租房信息");
System.out.println("带租客看房");
// 核心=原始业务类
fangDongService.zufang();
System.out.println("售后");
}
}
代理类=实现原始类相同接口+添加辅助功能+调用原始类的业务方法
静态代理的问题
代理数量过多不利于项目的管理
多个代理类的辅助功能代码冗余,修改时,维护性差
总结:
- 代理间接执行核心业务,扩展非核心业务。
- 代理模式很好的将核心业务与公共的非核心业务隔离开,方便集中管理。
- 缺点是一个代理角色只代理一个真实角色。
动态代理设计模式
为了解决静态代理中一个代理只代理一个真实角色的问题,我们可以使用动态代理来解决。动态代理中的代理类是动态生成的,不是直接写好的。底层是通过反射实现的
动态代理分为2大类:
- 基于接口的动态代理:
- JDK的动态代理
- 基于类的动态代理:
- cglib
- java字节码:javasist(jboss)
动态创建代理对象,为原始类的对象添加辅助功能
JDK动态代理实现(基于接口)
- 代理者的角色已经没有了,而是由Proxy动态生成。
- 生成的代理者需要实现抽象角色的核心业务,这样才知道要代理什么核心业务
- 调用代理者的代理业务时不直接执行,而且间接通过InvocationHandler中方法invoke反射执行
- InvocationHandler的作用理解为监控并执行代理者的核心业务方法的调用,监控意味着可以在核心业务中增强非核心的业务。
- Proxy是JDK中的类,InvocationHandler是JDK中的接口
//目标
FangDongService fangDongService=new FangDongServiceImpl();
//额外功能 设置回调函数
InvocationHandler ih=new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//辅助功能,额外功能
System.out.println("发布租房信息");
System.out.println("带租客看房");
// 核心=原始业务类
fangDongService.zufang();
return null;
}
};
//动态生成 代理类
FangDongService proxy= (FangDongService)Proxy.newProxyInstance(test.class.getClassLoader(), fangDongService.getClass().getInterfaces(), ih);
proxy.zufang();
详细版
定义核心业务
public interface Business {
// 核心业务:房屋买卖
public void buySellHouse() ;
}
定义被代理者
public class HouseOwner implements Business {
// 业务才能进行核心业务
@Override
public void buySellHouse() {
System.out.println("进行房屋买卖业务....");
}
}
定义InvocationHandler的实现类
public class ProxyInvocationHandler implements InvocationHandler{
private Business business ;
public void setBusiness(Business business) {
this.business = business;
}
//调用代理类的核心业务方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 扩展非核心业务
System.out.println("打广告……");
System.out.println("带人看房……");
// 间接执行被代理者的核心业务
Object result = method.invoke(business, args);
// 扩展非核心业务
System.out.println("售后……");
return result;
}
}
测试
// 定义真实角色
HouseOwner ho = new HouseOwner();
// 定义InvocationHandler并把代理接口注入进去
ProxyInvocationHandler handler = new ProxyInvocationHandler();
handler.setBusiness(ho);
// 生成代理类
Business proxy = (Business) Proxy.newProxyInstance(ho.getClass().getClassLoader(), ho.getClass().getInterfaces(), handler);
// 调用生成的代理类的代理方法
proxy.buySellHouse();
优化
public class ProxyInvocationHandler implements InvocationHandler {
private Object target ;
public Object getProxy(Object target){
this.target = target;
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("房屋推广……");
System.out.println("看房……");
Object obj = method.invoke(target, args);
System.out.println("收中介费……");
System.out.println("房屋售后……");
return obj;
}
}
测试
HouseOwner ho = new HouseOwner();
ProxyInvocationHandler handler = new ProxyInvocationHandler();
House house = (House) handler.getProxy(ho);
house.houseBuySell();
CGlib动态代理实现(基于继承)
- 虚线的代理者是由cglib中的Enhancer生成的。
- 代理者为动态生成的,它是被代理者是子类。
- 代理者继承之后会重写被代理者的方法,然后调用MethodInterceptor中的intercept方法。
导包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
//目标
FangDongService fangDongService =new FangDongServiceImpl();
//创建字节码增强对象
Enhancer enhancer=new Enhancer();
//设置父类(等价于实现原始类接口)
enhancer.setSuperclass(FangDongServiceImpl.class);
enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//辅助功能,额外功能
System.out.println("发布租房信息");
System.out.println("带租客看房");
// 核心=原始业务类
fangDongService.zufang();
return null;
}
});
// 动态生成代理类
FangDongServiceImpl proxy=(FangDongServiceImpl) enhancer.create();
proxy.zufang();
完整
定义MethodInterceptor
public class ProxyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object obj, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
// 扩展非核心业务
System.out.println("打广告……");
System.out.println("带人看房……");
// 间接执行被代理者的核心业务
Object o = methodProxy.invokeSuper(obj, objects);
// 扩展非核心业务
System.out.println("售后……");
return o;
}
}
测试
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\");
// 通过CGLIB的Enhancer对象来动态创建代理类
Enhancer enhancer = new Enhancer() ;
// 这一步就是告诉cglib,生成的子类需要继承哪个类
enhancer.setSuperclass(HouseOwner.class);
// 设置回调对象,在回调方法中监控并增强代理方法
enhancer.setCallback(new ProxyMethodInterceptor());
/* 创建代理对象,代理类是被代理的子类,多态
第一步、生成源代码
第二步、编译成class文件
第三步、加载到JVM中,并返回被代理对象 */
HouseOwner ho = (HouseOwner) enhancer.create();
// 通过代理对象调用目标方法
ho.buySellHouse();
优化
public class ProxyMethodInterceptor implements MethodInterceptor {
public Object getProxy(Class clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
// 扩展非核心业务
System.out.println("打广告……");
System.out.println("带人看房……");
// 反射父类(真实角色)的方法,如果写成invoke会死循环调用
Object result = methodProxy.invokeSuper(obj, objects);
// 扩展非核心业务
System.out.println("售后……");
return result;
}
}
总结
ProxyMethodInterceptor interceptor = new ProxyMethodInterceptor() ;
HouseOwner proxy = (HouseOwner) interceptor.getProxy(HouseOwner.class);
proxy.houseBuySell();
- JDK动态代理是基于接口,而cglib是基于类。
- 一般采用JDK方式,因为这种方式效率比较高。
AOP
AOP(Aspect oriented programming)即面向切面编程,即利用一种成为横切的技术,抛开封装的对象内部,并将
那些影响多个类的公共行为封装到一个可重用的模块,将其命名为Aspect 即切面,简单说就是那些与业务无关,却
为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的
可操作性和可维护性
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…
- 切面(Aspect):横切关注点 被模块化的特殊对象。即,它是一个类。(Log类)
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。(Log类中的方法)
- 目标(Target):被通知对象。(生成的代理类)
- 代理(Proxy):向目标对象应用通知之后创建的对象。(生成的代理类)
- 切入点(PointCut):切面通知执行的”地点”的定义。(最后两点:在哪个地方执行,比如:method.invoke())
- 连接点(JointPoint):与切入点匹配的执行点。
作用
soring的AOP编程即是通过动态代理为原始类的方法添加辅助功能
依赖
<!--spring AOP的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
spring-context.xml引入AOP命名空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util
https://www.springframework.org/schema/util/spring-util.xsd">
</beans>
< aop:config> 元素及其子元素
元素名称 | 用途 |
< aop:config> | 在配置文件的beans下可以包含多个该元素 |
< aop:aspect> | 配置一个切面,< aop:config>元素的子元素,属性ref指定切面的定义 |
< aop:pointcut> | 配置切入点,aop:aspect 元素的子元素,属性expression指定那些 |
< aop:advisor> | 定义通知器(通知器跟切面一样,也包括通知和切点) |
定义通知类(添加额外功能)
package com.blb.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
//辅助功能,额外功能
System.out.println("发布租房信息");
System.out.println("带租客看房");
}
}
具体选择前置,后置还是环绕,主要是根据需求来,例如租房,只有在发布信息,带租客看房之后,才能,签合同租房
定义bean标签
<!-- 原始对象-->
<bean id="fangdongservice" class="com.blb.service.FangDongServiceImpl"></bean>
<!-- 辅助对象-->
<bean id="beforedvice" class="com.blb.advice.BeforeAdvice"></bean>
定义切入点,形成切面
<!-- 定义切入点-->
<aop:config>
<!-- 切入点 [修饰符 返回值 包.类 方法名 参数列表]-->
<aop:pointcut id="pc_fangdong" expression="execution(* zufang())"/>
<!-- 组装-->
<aop:advisor advice-ref="beforedvice" pointcut-ref="pc_fangdong"></aop:advisor>
</aop:config>
ApplicationContext context=new ClassPathXmlApplicationContext("/spring-context.xml");
FangDongService proxy = (FangDongService)context.getBean("fangdongservice");
System.out.println(proxy.getClass());
proxy.zufang();
通知类
定义通知类,达到通知效果
前置通知 MethodBeforeAdvice //在核心之前执行
后置通知 AfterAdvice //是后置通知和异常通知的父类,核心之后执行
后置通知 AfterReturningAdvice //有异常不执行,方法会员异常而结束,无返回值
异常通知 ThrowsAdvice //抛出异常才执行
环绕通知 MethodInterceptor //核心的前后分别执行
MethodInterceptor
package com.blb.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeAdvice implements MethodBeforeAdvice{
//method:要执行的目标对象的方法
//args:参数
//target:目标对象
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
//辅助功能,额外功能
System.out.println("发布租房信息");
System.out.println("带租客看房");
}
}
AfterReturningAdvice
package com.blb.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterAdvice implements AfterReturningAdvice {
//后置通知,在核心之后执行,如果核心有异常,则不执行
//returnVaule: 返回值
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("after入住");
}
}
ThrowsAdvice
package com.blb.advice;
import org.springframework.aop.ThrowsAdvice;
public class Throwsadvice implements ThrowsAdvice {
//在核心中抛出异常会执行
public void afterThrowing(Exception e){
System.out.println("异常");
}
}
注意:该消息必须在方法中抛出异常
package com.blb.service;
public class FangDongServiceImpl implements FangDongService{
@Override
public void zufang() {
// 核心功能
System.out.println("签合同");
System.out.println("租房");
if(1==1){
throw new NullPointerException("空指针");
}
}
}
MethodInterceptor
package com.blb.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class Methodinterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("begin");
Object ret= methodInvocation.proceed();//触发,执行核心
System.out.println("end");
return ret;
}
}
通配切入点
根据表达式统配切入点
execution(修饰符 返回类型 切入点类 切入点方法(参数) 异常抛出)
<aop:config>
<!-- 匹配参数-->
<aop:pointcut id="user2" expression="execution(* *(com.blb.entity.User))"/>
<!--匹配方法名(无参)-->
<aop:pointcut id="user3" expression="execution(* queryUser())"/>
<!--匹配方法名(任意参数)-->
<aop:pointcut id="user4" expression="execution(* queryUser(..))"/>
<!--匹配返回值类型 -->
<aop:pointcut id="user5" expression="execution(java.util.List *(..))"/>
<!--匹配类名-->
<aop:pointcut id="user6" expression="execution(* com.blb.service.UserService2Impl.*(..))"/>
<!--匹配包名-->
<aop:pointcut id="user7" expression="execution(* com.blb.service.*.*(..))"/>
<!--匹配包名,以及子包名 -->
<aop:pointcut id="user8" expression="execution(* com.blb..*.*(..))"/>
<aop:advisor advice-ref="beforeadvice" pointcut-ref="user8"></aop:advisor>
</aop:config>
AOP小结
通过AOP提供的编码流程,更便利的定制切面,更方便的定制动态代理
进而彻底解决了辅助代码功能冗余的问题
业务类中职责单一性得到更好的保障
辅助功能也有更好的复用性
JDK和CGLIB选择
spring底层,包含了JDK代理和cglib代理两种动态生成机制
基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用cglib代理
后处理器
整合mybatis
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring01</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>13</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<!--spring AOP的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
将数据资源配置到项目
引入jdbc.properties配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db3?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123456
jdbc.init=1
jdbc.minIdle=1
jdbc.maxActive=10
整合spring配置文件和properties配置文件
Druid连接池可选参数
<!--读取jdbc.properties 配置文件参数化(参数占位符)-->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 集成Druid连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 基本配置-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<!-- 配置初始化大小,最大,最小-->
<property name="initialSize" value="${jdbc.init}"></property>
<property name="minIdle" value="${jdbc.minIdle}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
<!-- 配置获取连接等待超时时间-->
<property name="maxWait" value="60000"></property>
<!-- 配置间隔多久时间才能进行一次检测,检测需要关闭的空闲连接,单位是毫秒-->
<property name="timeBetweenEvictionRunsMillis" value="60000"></property>
<!-- 配置一个连接池中最小生存时间,单位是毫秒-->
<property name="minEvictableIdleTimeMillis" value="300000"></property>
</bean>
导入依赖
//srping-jdbc
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
//srping+mybatis集成依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.3</version>
</dependency>
配置SqlSessionFactory
<!-- 生产sqlsessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入连接池-->
<property name="dataSource" ref="dataSource"></property>
<!-- 注入Mapper文件信息,如果映射文件和Mapper接口,同包且同名,则此配置可以省略-->
<property name="mapperLocations">
<list>
<value>classpath:com/blb/dao/*.xml</value>
</list>
</property>
<!-- 定义别名-->
<property name="typeAliasesPackage" value="com.blb.entity"></property>
</bean>
配置MapperScannerConfigurer
管理dao里面的Mapper实现类的创建,并创建Mapper对象,存入工厂管理
扫描所有DAO接口,去构建DAO实现
将DAO实现存入工厂管理
DAO实现对象在工厂中的id是:首字母小写-接口的类名
例如 UserDAO===》userDAO
OrderDAO===》orderDAO
dao接口所在的包,如果有多个包逗号或分号分隔
<property name="basePackage" value="com.a.dao,com.b.dao"></property>
<bean id="MapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.blb.dao"></property>
<!-- 如果工厂只有一个sqlsessionfactory的bean此配置可以省略-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
配置service
package com.blb.service;
import com.blb.dao.StudentMapper;
import com.blb.entity.student;
import java.util.List;
public class StudentServiceImpl implements StudentService{
private StudentMapper studentMapper;
public StudentMapper getStudentMapper() {
return studentMapper;
}
public void setStudentMapper(StudentMapper studentMapper) {
this.studentMapper = studentMapper;
}
@Override
public List<student> querystus() {
return studentMapper.querystus();
}
}
<bean id="studentService" class="com.blb.service.StudentServiceImpl">
<property name="studentMapper" ref="studentMapper"></property>
</bean>
事务
配置DataSourceTransactionManager
<!-- 引入一个事务管理器,其中依赖DataSource,借以获取连接,进而控制事务逻辑-->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
注意 DataSourceTransactionManager和SqlSessionFactoryBean 要注入同一个DataSource的bean,否则事务控制失效
配置事务通知
基于事务管理器,进一步定制,生成一个额外的功能:Advice
此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务
<tx:advice id="txManger" transaction-manager="tx">
<tx:attributes>
<!-- 以User结尾的方法,切入此方法时,采用对应事务实行-->
<tx:method name="*User" rollback-for="Exception"/>
<!-- 以query开头的方法,切入此方法时,采用对应事务实行-->
<tx:method name="query*" propagation="SUPPORTS"></tx:method>
<!-- 剩余所有方法-->
<tx:method name="*"></tx:method>
</tx:attributes>
</tx:advice>
事务属性
隔离级别
isolation 隔离级别
名称 | 级别 |
default | (默认值)采用数据库默认的设置 |
READ_COMMITTED | 读提交(oracle数据库默认的隔离界别) |
READ_UNCOMMITTED | 读未提交 |
REPEATABLE_READ | 可重复读(MYSQL数据库默认的隔离级别) |
SERIALIZABLE | 序列化读 |
隔离界别由低到高为:
READ_COMMITTED <READ_COMMITTED <REPEATABLE_READ <SERIALIZABLE
特性
安全性:级别越高,多事务并发时,越安全,因为共享的数据越来越少,事务间彼此干扰减少
并发性:级别越高,多事务并发时,并发越差,因为共享的数据越来越少,事务间阻塞情况增多
并发问题
事务并发时的安全问题
问题 | 描述 |
脏读 | 一个事务读取到另一个事务还未提交的数据,大于等于READ_COMMITTED 可防止 |
不可重复读 | 一个事务内多次读取一行数据的内容,其结果不一致,大于等于REPEATABLE_READ 可防止 |
幻影读 | 一个事务内多次读取一张表中相同内容,其结果不一致SERIALIZABLE 可防止 |
传播行为
propagation 传播行为
当涉及到事务嵌套(Service调用service)时可以设置
SUPPORTS=不存在外部事务,则不开启新事务,存在外部事务,则合并到外部事物中(适合查询)
REQUIRED=不存在外部事务,则开启新事务,存在外部事物,则合并到外部事务中,(默认值,适合增删改)
读写性
read-only 读写性
true :只读 可以提高事务效率 (适合查询)
false :可读可写 (默认值,适合增删改)
事务超时
timeout="" 事务超时时间
当前事务所需要操作的数据被其他事务占用,则等待
xx 自定义等待时间xx(秒)
-1 由数据库指定等待时间,(默认值)
事务回滚
rollback-for 回滚属性
如果抛出RuntimeException,则自动回滚
如果抛出CheckExcpton(非运行时异常),不会自动回滚,而是默认提交事务
处理方案将CheckExcpton转换成RunntimeExcpton上抛,或者设置rollback-for="Exception"
编织
将事务管理的Advice切入所需要的业务方法中
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.blb.service.StudentServiceImpl.*(..))"/>
<aop:advisor advice-ref="txManger" pointcut-ref="pc"></aop:advisor>
</aop:config>
注解开发
除了在applicationContext.xml中配置装配信息,我们还可以通过注解的方式来配置装配信息。
配置注解的支持
<context:annotation-config />
定义要扫描的包
<context:component-scan base-package="com.blb.spring.bean" />
扫描com.blb.spring.bean包,让这个包下的所有注解生效。可以定义多个包用逗号隔开。
声明bean
用于替换自建类型组件的< bean>,可以快速的声明bean
@Service 业务类专用
@Repository dao实现类专用
@Controller web层专用
@Component 通用
@Scope 用户控制bean的创建方式
@Component
@Component
注解用来声明bean,相当于配置文件中的<bean id="" class="" />
,其中id值默认为当前类的类名的首字母小写,class默认为当前类的类型。如果不想使用默认的id值,可以加参数来自定义,方式为@Component("newName")
@Service
声明bean,此类以一个业务类,需要将此类纳入工厂,可以等价替换掉 < bean >标签
这个注解是给类添加的
@Service
public class UserServiceImpl implements UserService{
}
//@Service默认beanId==首字母小写的类名 userServiceImpl
//@Service("xxx") 自定义beanId为xxx
@Controller、@Repository基本与@Service()一样,这3个注解的作用同@Component,只是分别用在控制层、服务层、DAO层。语义化可读性强。
@Scope
声明创建的模式,默认为单例模式
@Scope
public class UserServiceImpl implements UserService{
}
@Scope("singleton") 声明创建的模式为单例模式
@Scope("prototype") 声明创建模式为多例模式
注入(DI)
用于完成bean中属性值的注入
@Autowired 基于类型自动注入
@Resource 基于名称自动注入
@Qualifier("userDAO") 限定要自动注入的bean的id,一般和@Autowired一起使用
@Value 注入简单类型数据(jdk8种+string)
@Autowired不能唯一装配时,需要@Autowired+@Qualifier
@Resource
名称自动注入
@Resource
private UserDao userDao;
如果不加name会默认找和属性同名的bean
@Resource (name="xx")会在工厂中找一个id为xx的bean
如果爆红则需要引入依赖
<!--注解依赖 https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
@Autowired
基于类型自动注入
要么找不到,要么找到多个报错
基于类型自动注入,并挑选Beanid=userDAO
@Autowired
@Qualifier("userDAO")
@Value
将值注入到bean中,可以定义在属性上,也可以定义在set方法或者其它方法上。
@value("100") //注入数字
private Integer id;
@value("dyk") //注入string
private String name;
@Component
public class User {
//相当于<property name="name" value="dyk"/>
@value("dyk")
public String name;
//也可以放在set方法上面
//@value("dyk")
public void setName(String name) {
this.name = name;
}
}
- @Value可以用在属性上注入,也可以用在方法上注入。
- 方法注入时方法名不限制,注入方法会调用。
- 属性注入时不依赖于构造方法跟setter方法
区别:
@Resource和@Autowired的区别:
- 都是用来自动装配的,都可以放在属性字段上
- @Autowired通过byType的方式实现,而且必须要求这个对象存在!【常用】
- @Resource默认通过byname的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!【常用】
- 执行顺序不同:@Autowired通过byType的方式实现。@Resource默认通过byname的方式实现
- @Autowired来自Spring,@Resource来自JDK
小结:
- 需要配置注解的支持。
- 注解装配的机制是直接反射,不依赖于bean类的set方法跟构造方法。
- 自动装配会在配置文件中查看到需要的bean然后帮我们装配,所以配置文件中需要有符合byType或者byName模式的bean。
- 注解装配的方式要求必须装配上,如果没有符合要求的bean来装配则会报错,除非在方法上使用require=false参数:
@Autowired(required=false)
- 如果
@Autowired
在配置文件中通过byName匹配不上,通过byType有多个匹配上的话,可以配合注解@Qualifier(value="course2")
来定义我们要装配那个id属性值对应的bean。 @Autowired
注解是来自spring的,我们还可以通过@Resource
注解来实现。作用相同。@Resource(name="course2")
同@Autowired
+@Qualifier(value="course2")
JavaConfig
JavaConfig是Spring的一个子项目,在Spring4之后成为了一个核心功能,主要作用是不用配置文件,全部在java中配置完成
entity
@Component
public class Student {
@Value("seven")
private String name ;
@Value("18")
private int age ;
// setter/getter/toString
}
AppConfig
//@Configuration代表一个配置类(applicationContext.xml),本质上也是一个Component
@Configuration
// @ComponentScan扫描某个包,相当于<context:component-scan />
@ComponentScan("com.blb.entity")
// @Import导入另一个配置类,相当于<import />
@Import(AppConfig2.class)
public class AppConfig {
/**
* @Bean标签相当于<bean />
* id值默认为方法名,class默认为返回对象的类型
*/
@Bean
public Student student(){
return new Student();// 返回的对象实例注册到容器中
}
}
@Test
public void testStudent(){
//使用的是java注解配置方式,需要使用AnnotationConfigApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Student s = context.getBean("student",Student.class);
System.out.println(s);
}
- @Configuration等同于配置文件中的beans标签
- @Bean等同于配置文件中的bean标签
- @ComponentScan扫描某个包,让包下的注解生效
- @Import导入多个带@Configuration注解的配置类
事务控制
用于控制事务切入
@Transactional
工厂配置中的<tx:advice>和<aop:config>可以省略
可以给类添加也可以给类对应的方法添加
可以给类添加各类中的每个方法都切入事务(有自己的事务控制的方法除外)
给方法添加该方法的事务控制,仅对此方法有效
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED,readOnly = false,rollbackFor = Exception.class,timeout = -1)
注解所需配置
<!-- 告知spring,那些包中有别注解的类,方法,属性-->
<!-- 如果有多个包逗号或分号分隔<context:component-scan base-package="com.blb,com.xxx"></context:component-scan>-->
<context:component-scan base-package="com.blb"></context:component-scan>
<!--告知spirng@transactional在制定事务时基于tx= DataSourceTransactionManager -->
<tx:annotation-driven transaction-manager="tx"></tx:annotation-driven>
aop
注解使用
package com.blb.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect//声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件进入工厂
public class myaspect {
//定义切入点
@Pointcut("execution(* *(..))")
public void pc(){}
@Before("pc()")//前置通知
public void mybefore(JoinPoint a){
System.out.println("target"+a.getTarget());
System.out.println("args"+a.getArgs());
System.out.println("method name"+a.getSignature().getName());
System.out.println("before...");
}
@AfterReturning(value = "pc()",returning="ret")//后置通知
public void myAfterReturning(JoinPoint a,Object ret){
System.out.println("after....");
}
@Around("pc()")
public Object myInterceptor(ProceedingJoinPoint p) throws Throwable {
System.out.println("begin...");
Object ret=p.proceed();
System.out.println("end...");
return ret;
}
@AfterThrowing(value = "pc()",throwing = "ex")//异常通知
public void myThrows(JoinPoint jp,Exception ex){
System.out.println("throws...");
System.out.println(ex.getMessage());
}
}
或者直接在注解里面配置
@Aspect //标注这个类是一个切面
public class myaspect {
@Before("execution(* service.UserServiceImpl.*(..))")
public void before(){
System.out.println("=====方法执行前=====");
}
@After("execution(* service.UserServiceImpl.*(..))")
public void after(){
System.out.println("=====方法执行后=====");
}
//在环绕增强中,我们可以给地暖管一个参数,代表我们要获取切入的点
@Around("execution(* service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
Object proceed = joinPoint.proceed();
System.out.println("环绕后");
}
}
配置
<!-- 告知spring,那些包中有别注解的类,方法,属性-->
<!-- 如果有多个包逗号或分号分隔<context:component-scan base-package="com.blb,com.xxx"></context:component-scan>-->
<context:component-scan base-package="com.blb"></context:component-scan>
<!--添加如下配置,启用aop注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
使用java的方式来配置spring
//这里这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中
@component
public class User {
private String name;
public String getName() {
return name;
}
//属性注入值
@value("dyk")
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "user{" +
"name='" + name + '\''+
'}';
}
}
要么使用@Bean,要么使用@Component和ComponentScan,两种效果一样
//这个也会Spring容器托管,注册到容器中,因为他本来就是一个@Component
// @Configuration表这是一个配置类,就像我们之前看的beans.xml,类似于<beans>标签
@Configuration
@componentScan("com.blb.dao") //开启扫描
public class Config {
//注册一个bean , 就相当于我们之前写的一个bean 标签
//这个方法的名字,就相当于bean 标签中的 id 属性 ->getUser
//这个方法的返同值,就相当于bean 标签中的class 属性 ->User
//@Bean
public User getUser(){
return new User(); //就是返回要注入到bean的对象!
}
}
@Bean是相当于< bean>标签创建的对象,@Component是通过spring自动创建的这个被注解声明的对象,所以这里相
当于有两个User对象被创建了。一个是bean标签创建的(@Bean),一个是通过扫描然后使用@Component,spring自
动创建的User对象,所以这里去掉@Bean这些东西,然后开启扫描。之后在User头上用@Component即可达到spring自
动创建User对象了
spring集成JUnit
导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
编码
可以免去工厂创建过程
可以直接将要测试的主键注入到测试类
package com.blb.tests;
import com.blb.dao.UserDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)//由SpringJUnit4ClassRunner启动测试
@ContextConfiguration("classpath:spring-context.xml")
public class testjunit {//当前测试类也会被纳入工厂中,所以其中属性可以注入
@Autowired//注入要测试的组件
@Qualifier("userDao")
private UserDao userDao;
@Test
public void test()
{
userDao.deleteUser(1);
}
}