Spring
*核心概念:
-
IOC(Inversion of Control)控制反转:
目的:为了降低程序间的耦合度
含义:把程序内主动new创造对象的过程交给外部程序去做,创建对象的过程中控制权由程序部转到了*外部*去实现。
Spring对
Ioc
思想进行了实现:其提供了IOC
容器来充当IOC
思想的“外部”,IOC
负责创建和管理对象,被创建和管理的对象在IOC
中称为Bean
。 -
DI(Dependence Injection) 依赖注入:
含义:在容器中建立bean之间的依赖关系。
-
效果
:使用对象是不仅可以直接从容器中取,还能获取到bean绑定的所有依赖关系。
1:IOC入门案例:
-
在
pom.xml
配置文件中导入依赖<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency>
-
在
resourse
文件夹中建立Spring配置文件applicationContext.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"> <!--1:导入spring的坐标spring-context,对应版本为5.2.10.RELEASE--> <!--2:配置bean--> <!--bean:配置bean--> <!--id:给bean起的别名--> <!--class:给bean定义类型--> <bean id="bookDao" class="com.ysj.dao.impl.BookDaoImpl"> </bean> <bean id="bookService" class="com.ysj.service.impl.BookServiceImpl"> </bean> </beans>
-
在测试类中:
//3:加载配置文件,获取IOC容器对象 ApplicationContext ext = new ClassPathXmlApplicationContext("applicationContext.xml"); //4:获取bean // BookDao bookDao = (BookDao) ext.getBean("bookDao"); // bookDao.save();; BookService bookService = (BookService) ext.getBean("bookService"); bookService.save();
-
入门案例中,此时在业务层实现中,仍存在new创建对象,耦合度相对来说仍然较高,接下来就到
DI(Dependence Injection)
登场了。
2:DI(Dependence Injection)入门案例:
-
//7:删除new private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } //8:设置setter方法:容器创建对象会调用setter方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; }
<bean id="bookService" class="com.ysj.service.impl.BookServiceImpl">
<!-- 配置server与dao的关系(dao对象在server中创建,所以在server中配置)-->
<!-- property:配置当前bean的属性,即对应类中的属性-->
<!-- name:当前bean所对应的类中的具体属性-->
<!-- ref:表示参照哪个bean进行配置-->
<property name="bookDao" ref="bookDao"></property>
</bean>
3:bean
3:bean的基础配置
3.1:name属性:给bean起别名,多个别名间用空格隔开
-
<bean id="bookDao" name="dao ss ss1" class="com.ysj.dao.impl.BookDaoImpl">
3.2:bean默认是单例模式
<!-- scope:选择bean是否为单例模式
prototype:多例
singleton:单例-->
<bean id="bookDao" name="dao ss ss1" scope="prototype"
class="com.ysj.dao.impl.BookDaoImpl">
3.3🏖bean默认是单例模式的原因
如果默认不是单例,那么创造的bean对象将会无穷无尽,增加了Spring的负担 ##适合交给容器进行管理的bean 1:表现层对象 2:业务层对象 3:数据层对象 4:工具类对象 ##不适合交给容器进行管理的bean 封装实体类的域对象
3.4:常见报错
//原因:没有命名为‘ss2’的bean
//方法;检查bean容器别名和自己书写哪里不一致
NoSuchBeanDefinitionException: No bean named 'ss2' available
4:bean的实例化的三种方式
4.1:构造方法(常用)
##提供可访问的构造方法:无参(bean容器创建对象调用无参构造方法)
##如果没有无参构造方法将报‘BeanCreationException’,‘java.lang.NoSuchMethodException’异常
4.2:Spring报错处理方法
#从后往前看
4.3:静态工厂(了解)
//静态工厂创建对象
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");
return new OrderDaoImpl();
}
}
<!-- 方式二:使用静态工厂实例化bean,记得配置工厂造对象的具体方法-->
<bean id="orderDao" class="com.ysj.factory.OrderDaoFactory" factory-method="getOrderDao"/>
4.4:实例工厂
4.4.1:实例工厂实例化(了解)
- 第一个bean仅仅是为了配合使用,创造对象,除此之外无其他意义
- factory-method:如果这样配置,方法名不固定,每次配置bean都要配置
<!-- 方式三:使用实例工厂实例化bean-->
<bean id="userFactory" class="com.ysj.factory.UserDaoFactory"/>
<!--factory-bean:配置工厂类的bean-->
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
4.4.2:FactoryBean
(实用)
//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法,bean中不必再配置factory-method,统一使用该方法创建对象
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
//true:创建对象是单例
//false:创建对象非单例
public boolean isSingleton() {
return false;
}
}
<!--方式四:使用FactoryBean实例化bean-->
<bean id="userDao" class="com.ysj.factory.UserDaoFactoryBean"/>
5:bean的生命周期
5.1:bean的初始化和销毁方法的配置
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destroy...");
}
}
<!--init-method:设置bean初始化生命周期回调函数-->
<!--destroy-method:设置bean销毁生命周期回调函数,仅适用于单例对象-->
<bean id="bookDao" class="com.ysj.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
配置完后运行程序,会发现destroy方法没有运行,这时你大叫:“Why?”
其实是因为:我们这些程序运行在虚拟机上,当IOC
容器加载完配置后,bean也初始化了,接着运行程序,
然而当程序运行完后,虚拟机退出了,俺类destroy并没有机会执行,呜呜呜呜~~
public static void main( String[] args ) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
那么,如何让俺滴销毁方法执行呢?
有如下两个方法:
5.2:让销毁执行起来
注意把在加载IOC
容器时定义ClassPathXmlApplicationContext
对象
5.2.1:手动close(太暴力了,直接就停了)
用这个要注意使用的位置
public static void main( String[] args ) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
//关闭容器
ctx.close();
}
5.2.3:注册关闭钩子函数
虚拟机会在退出之前调用此函数,进而关闭容器
public static void main( String[] args ) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
//注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
ctx.registerShutdownHook();
}
5.2:接口控制(了解)
按照Spring官方给的接口和规范进行配置
实现InitializingBean, DisposableBean
接口
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
//在资源加载完后运行,但是在调用方法之前
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
public void setBookDao(BookDao bookDao) {
System.out.println("set .....");
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
public void destroy() throws Exception {
System.out.println("service destroy");
}
}
5.3:生命周期历程
-
初始化容器
- 创建对象(内存分配,类似于new,在内存中分配了空间)
- 执行构造方法(对象创建完成)
- 执行属性注入(set操作)
- 执行bean的初始化方法
-
使用bean
执行业务层
-
关闭/销毁容器
执行bean的销毁方法
5.4:小记
所有bean在IOC
容器加载完配置后就被初始化了
4:依赖注入
4.1:setter注入
<!--注入简单类型-->
<bean id="bookDao" class="com.ysj.dao.impl.BookDaoImpl">
<!--property标签:设置注入属性-->
<!--name属性:设置注入的属性名,实际是set方法对应的名称-->
<!--value属性:设置注入简单类型数据值-->
<property name="connectionNum" value="100"/>
<property name="databaseName" value="mysql"/>
</bean>
<!--注入引用类型-->
<bean id="bookService" class="com.ysj.service.impl.BookServiceImpl">
<!--property标签:设置注入属性-->
<!--name属性:设置注入的属性名,实际是set方法对应的名称-->
<!--ref属性:设置注入引用类型bean的id或name-->
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
4.2🥊构造器注入
<!-- 标准书写-->
<bean id="bookDao" class="com.ysj.dao.impl.BookDaoImpl">
<!-- 根据构造方法参数名称注入-->
<constructor-arg name="connectionNum" value="10"/>
<constructor-arg name="databaseName" value="mysql"/>
</bean>
<bean id="userDao" class="com.ysj.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.ysj.service.impl.BookServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
<!-- 解决形参名称的问题,与形参名不耦合(了解)-->
<bean id="bookDao" class="com.ysj.dao.impl.BookDaoImpl">
<!-- 根据构造方法参数类型注入-->
<constructor-arg type="int" value="10"/>
<constructor-arg type="java.lang.String" value="mysql"/>
</bean>
<bean id="userDao" class="com.ysj.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.ysj.service.impl.BookServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
<!-- 解决参数类型重复问题,使用位置解决参数匹配-->
<bean id="bookDao" class="com.ysj.dao.impl.BookDaoImpl">
<!--根据构造方法参数位置注入-->
<constructor-arg index="0" value="mysql"/>
<constructor-arg index="1" value="100"/>
</bean>
<bean id="userDao" class="com.ysj.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.ysj.service.impl.BookServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
4.3:依赖注入方式选择
1:铭记一点:推荐使用setter注入
2:Spring官方和第三返回给机构使用构造器是为了严谨
3:看具体项目,只有构造器注入的话也只能用了
4.4:自动装配
byType
:按类型装配(容器中相同类型的bean必须唯一,推荐推荐)byName
:按名称装配(容器中要有相应名称的bean,因为变量名与配置的名称耦合,不推荐)- 自动装配适用于引用类型的注入,并不能对简单类型进行操作
- 自动装配的优先级低于setter注入和构造器注入,同时有可能出现自动装配失效
<!--这个id="bookDao"可以不写-->
<bean id="bookDao" class="com.ysj.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.ysj.service.impl.BookServiceImpl" autowire="byType"/>
4.5:集合注入
着重记下map
<bean id="bookDao" class="com.ysj.dao.impl.BookDaoImpl">
<!--数组注入-->
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<!--list集合注入-->
<property name="list">
<list>
<value>itcast</value>
<value>ysj</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
<!--set集合注入-->
<property name="set">
<set>
<value>itcast</value>
<value>ysj</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
<!--map集合注入(重点)-->
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
<!--Properties注入-->
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
</bean>
5:案例:数据源对象管理
5.1:德鲁伊连接池管理(Druid)
第一步:先把相关的依赖导入
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- 导入Druid的坐标-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
第二部:现在也没啥事儿,利用Spring新建个bean对象测试呗
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource)
5.2:c3p0
连接池配置
第一步:同Druid
<!-- c3p0的配置,这里只做了基本的配置,其他的类似最大连接数的可自行配置-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="dbc:mysql://localhost:3308/mybatis"></property>
<property name="user" value="root"></property>
<property name="password" value="yangshijie"></property>
</bean>
第二步:同druid
5.3:加载properties配置文件
上面那样写在配置文件里写死真的好吗?
接下来我们在配置文件里进行配置
- 第一步:开启context命名空间
<context:property-placeholder location="jdbc.properties"/>
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
"
- 第二步:使用context命名空间加载properties文件
<context:property-placeholder location="jdbc.properties"/>
- 小记:properties文件里可能有与系统一样的变量名,设置system-properties-mode=“NEVER”,该变量才能被加载。但是现在这个
location="jdbc.properties"
也只能加载一个文件呀
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
- 下面施加在多个文件的,
location="jdbc.properties,jdbc2.properties"
中间价格逗号就可
<context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>
- 直接加载全部配置文件
location="*.properties,jdbc2.properties"
,但是呢,咱们这样写不太专业
<context:property-placeholder location="*.properties,jdbc2.properties" system-properties-mode="NEVER"/>
-
标准格式(但是它只能读取模块内的配置文件,一些第三方打包好的jar包里的咱们扫描不到)
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
-
下面是最标注的格式,都能读
location="classpath*:*.properties"
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>
6:容器
6.1:创建容器
6.2:获取bean
- 通过bean的名称获取,但是有强转(说实话,不太好)
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
- 先通过bean名称,在找到
BookDao.class
对应的类创建对象
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
- 直接通过类型创建,但是如果对应
BookDao.class
的bean有多个,那这一下就创建出来很多对象了。
BookDao bookDao = ctx.getBean(BookDao.class);
6.3:beanFactory
加载和ApplicationContext
加载的区别
-
beanFactory
:所有bean延迟加载(在加载配置文件时,没有创建对象,即没有执行构造方法) -
ApplicationContext
:立即加载 但是可以在配置bean的时候加上
lazy-init="true"
实现延迟加载的效果<bean id="bookDao" class="com.ysj.dao.impl.BookDaoImpl" lazy-init="true"/>
7:注解开发
7.1:配置bean
- 直接创建一个Spring配置类
- @Configuration:声明当前类为Spring配置类
- @ComponentScan:设置bean扫描路径
- @Component:定义当前类为bean
- @Service:业务层
- @Repository:表现层
- @Controller:控制层
//声明当前类为Spring配置类
@Configuration
//设置bean扫描路径,多个路径书写为字符串数组格式
@ComponentScan({"com.ysj.service","com.ysj.dao"})
public class SpringConfig {
}
-
改为配置类后,初始化容器对象就切换到读取Java配置类
//AnnotationConfigApplicationContext加载Spring配置类初始化Spring容器 ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
7.2:管理bean
@Scope("singleton") //配置该bean初始化对象是单例
@Scope("prototype") //多例
@PostConstruct //配置初始化方法
@PreDestroy //配置销毁方法
7.3:依赖注入
@Autowired //自动注入,适用于引用类型,默认按类型装配
@Qualifier://依赖于@Autowired,按bean名称进行自动装配
@Value: //适用于简单类型注入,@Value("${name}")可通过EL表达式获取配置文件里的属性值
@PropertySource({"jdbc.properties","jdbc2.properties"})
//加载配置文件,如果有多个,用大括号圈起来
//不允许使用通配符*
7.3.2:管理第三方bean
//1:定义一个方法获得要管理的对象
@Bean
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
System.out.println(druidDataSource);
return druidDataSource;
}
-
记得使用独立的配置类管理第三方bean
-
扫描式:给非Spring配置类也加上@Configuration让其变成配置类,接着在Spring配置类进行包扫描即可(不推荐,因为不方便读)
-
导入式:在Spring配置类中Import类(无需将该类变为配置类,推荐)
//@Import:导入配置信息 @Import({JdbcConfig.class})
-
为第三方bean注入资源
简单类型:简单粗暴,直接定义变量,赋值,然后再方法里用
@Value("com.mysql.jdbc.Driver") private String driver; @Value("jdbc:mysql://localhost:3306/spring_db") private String url; @Value("root") private String userName; @Value("root") private String password; //2.添加@Bean,表示当前方法的返回值是一个bean //@Bean修饰的方法,形参根据类型自动装配 @Bean public DataSource dataSource(BookDao bookDao){ System.out.println(bookDao); DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; }
假设我们一个bean中还需要使用第三方bean,怎么办?往下看啊!
-
引用类型:先在Spring配置类中加上包扫描(确保能扫描到要使用的第三方bean),然后直接在要使用第三方bean的方法中,加上该类bean类型的形参,Spring容器会根据形参类型自动装配bean。
//2.添加@Bean,表示当前方法的返回值是一个bean //@Bean修饰的方法,形参根据类型自动装配 @Bean public DataSource dataSource(BookDao bookDao){ System.out.println(bookDao); DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; }
7.4:Spring整合mybatis
- 导入相关依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
//1:先造出SqlSessionFactoryBean的对象
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//2:设置类型对象所在包
sqlSessionFactoryBean.setTypeAliasesPackage("com.ysj.domain");
//3:设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
//4:设置sql文件所在报的扫描路径
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.ysj.dao");
return mapperScannerConfigurer;
}
}
最后最后,千万别忘了把mybatis配置文件导入Spring配置类中
@Import({JdbcConfig.class,MybatisConfig.class})
7.5:Spring整合Junit
导入依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = SpringConfig.class)
//支持自动装配注入bean,按类型查找
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.findById(1));
}
8:AOP
8.1:入门案例
-
导入相关依赖,
AOP
的依赖在Spring-context中已经被包含了,所以只需要再导入切面依赖即可<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <!--切面依赖--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
-
自定义一个通知类(通知类必须配置成Spring管理的bean)
-
设置通知类为切面类
-
在Spring配置类中开启注解开发
AOP
功能 -
设置要作为增强功能的方法和切入点
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
//设置切入点,要求配置在方法上方
@Pointcut("execution(void com.ysj.dao.BookDao.update())")
private void pt(){}
//设置在切入点pt()的前面运行当前操作(前置通知)
// @Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
8.2:AOP
工作流程
- Spring容器启动
- 读取页面所有的切入点
- 初始化bean,判断bean中是否有方法匹配到任意切入点
- 匹配到,创建代理对象
- 未匹配到,创建初始的对象
- 获取bean的执行方法
- 获取bean,调用方法执行,完成操作
- 获取的bean是代理对象时,执行是按照代理对象的运行方式运行原生方法和
AOP
定义的增强方法
Eg
:在AOP
中,AOP
对最终的对象的ToString
方法进行了重写,如果直接输出对象则是类似下面这样
`com.ysj.dao.impl.BookDaoImpl@4988d8b8`
8.3:AOP
切入点表达式
-
*** ** :单个独立的任意符号,可独自出现,也可作为前缀或后缀的匹配符出现
-
… :多个连续的任意符号,可独立出现,用于简化包名与参数的书写
-
+
:专门用于匹配子类类型//表示:任意返回值类型,在为顶层的包结构下任意包中的以Dao结尾的接口或类的子类中以up开头的无参方法 @Pointcut("execution(* com.*..*Dao+.up*())")
8.4:AOP通知类型
-
@before(“原始方法”):在原始方法运行之前
-
@after():在原始方法运行之后
-
@Around():环绕原始方法运行,可定义位置
-
环绕通知必须依赖形参
ProceedingJoinPoint
才能在方法内部对原始方法进行调用 -
未使用
ProceedingJoinPoint
将跳过原始方法的执行 -
对原始方法接收返回值的话必须设置返回值类型为Object,不接收的话可void,也可Object
public Object around(ProceedingJoinPoint pjp)
-
因为无法预知原始方法是否抛出异常,所以环绕通知必须抛出
Throwable
异常public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before advice ..."); //表示对原始操作的调用 Object ret = pjp.proceed(); System.out.println("around after advice ..."); return ret; //返回原始方法执行的返回值 }
-
-
另外俩(了解):
//@AfterReturning:返回后通知,在原始方法执行完毕后运行,且原始方法执行过程中未出现异常现象 // @AfterReturning("pt2()") public void afterReturning() { System.out.println("afterReturning advice ..."); } //@AfterThrowing:抛出异常后通知,在原始方法执行过程中出现异常后运行 @AfterThrowing("pt2()") public void afterThrowing() { System.out.println("afterThrowing advice ..."); }
8.5:测试业务层接口执行效率
//设置环绕通知,在原始操作的运行前后记录执行时间
@Around("ProjectAdvice.servicePt()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
//获取执行的签名对象
Signature signature = pjp.getSignature();
//获取类类型(带路径)
String className = signature.getDeclaringTypeName();
//获取类名
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
}
8.6:通知获取数据
-
ProceedingJonPoint
:专用于环绕通知,是JoinPoint
子类,可以实现对原始方法的调用 -
JoinPoint
:用来描述切入点对象,但必须配置成通知方法中的第一个参数,可以实现对原始方法的调用 -
getArgs():用来获取原始方法中的参数,返回值是一个数组
-
在原始方法return后和抛出异常后
//设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同 @AfterReturning(value = "pt()",returning = "ret") public void afterReturning(JoinPoint jp,String ret) { System.out.println("afterReturning advice ..."+ret); } //设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同 @AfterThrowing(value = "pt()",throwing = "t") public void afterThrowing(Throwable t) { System.out.println("afterThrowing advice ..."+t); }
9:Spring事务
- 阳间小技巧:想想数据库事务
9.1:准备工作
-
在Spring篇配置类中注册事务驱动
@EnableTransactionManagement
-
在jdbc配置类中配置事务管理器
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }
-
给相关需要事务管理的接口方法配置事务
//配置当前接口方法具有事务 @Transactional public void transfer(String out,String in ,Double money) ;
-
开始测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTest { @Autowired private AccountService accountService; @Test public void testTransfer() throws IOException { accountService.transfer("Tom","Jerry",100D); } }
9.2:事务传播行为
- 当你在一个测试中测试两个两个甚至是多个事务,但是其中一个事务是必须实现的(无论程序是否发生错误),这个时候怎么办呢,只需要给这个事务的接口配置事务属性
propagation = Propagation.REQUIRES_NEW
就行了
//propagation设置事务属性:传播行为设置为当前操作需要新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
void log(String out, String in, Double money);
-
怎么设置事务遇到异常回滚呢?别慌,看我操作。
//下面设置的是,当事务遇到IO异常,就事务回滚。 //rollback:设置当前事务参与回滚的异常,默认非运行时异常不参与回滚 @Transactional(rollbackFor = IOException.class)