Spring基本内容
Spring概述
- Spring的两大核心:(控制反转)IoC 和(面向切面编程) AOP
- 整合开源的第三方框架和类库,是使用最多的Java EE 企业应用开源框架
- 不使用EJB
Spring 优势
- 方便解耦,简化开发
- AOP编程的支持
- 声明式事务的支持
- 方便程序的测试
- 方便集成各种优秀的框架
- 降低Java EE API 的使用难度,Spring 对其进行封装
- Java 源码是经典学习案例
Spring5 的新特性
- 该版本时基于jdk8编写的。
- @NonNull 注解和@Nullable注解表示可为空的参数以及返回值。这样就不会再编译时候处理空值,而不是在运行时抛出空指针异常。
- spring-cjl 封装日志。
- 从索引读取实体,在小于200没有明显的差异。在大项目会很快。
- Kolin语言的支持。它是一种支持函数式编程风格的面向对象语言。
- 响应式编程风格。新的响应式堆栈web框架。如RESTful
- Junit5的支持。
Spring 体系结构
pow依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<!--不知为何用不了@Resource,所以引入此maven坐标-->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
依赖 、耦合
- 耦合:方法间的依赖。
- 如何解耦:降低程序间的依赖关系。
- 开发中应该做到,编译期不依赖,运行时才依赖。
- 解耦的思路:使用反射创建对象,不使用避免使用new关键字。
- 使用工厂模式创建bean。
//编译期依赖
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//运行时依赖
Class.forName("com.mysql.jdbc.Driver");
IOC 控制反转
- 把控制权交给了工厂,降低程序
Spring IOC
- 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
ApplicationContext三个实现类
- 1.ClassPathXmlApplicationContext
- 可以加载classpath 下的配置文件,要求配置文件必须在classpath下。
- 2.FileSystemXmlApplicationContext
- 可以加载磁盘任意路径下的配置文件,但是必须要有权限。
- 3.AnnotationConfigApplicationContext
- 用于读取注解创建容器的。
ApplicationContext *
- 它在构建核心容器时,创建对象的策略是采用立即加载的方式。
BeanFactory
- 它在构建核心容器时,创建对象的策略是采用延迟加载的方式。
Bean
创建Bean 的三种方式
- 1.使用默认构造函数创建,没有默认构造函数,则无法创建。
- 在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
<bean id="userService" class="com.wei.service.impl.UserServiceImpl" ></bean>
- 2.使用普通工厂中的方法创建对象。(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="com.wei.factory.InstanceFactory"></bean>
<bean id="userService" factory-bean="instanceFactory" factory-method="getAccountService" />
- 工厂类InstanceFactory
package com.wei.factory;
import com.wei.service.UserService;
import com.wei.service.impl.UserServiceImpl;
public class InstanceFactory {
public UserService getAccountService(){
return new UserServiceImpl();
}
}
- 3.使用工厂中的静态方法创建对象,与2类似。
<bean id="userService" class="com.wei.factory.StaticFactory" factory-method="getAccountService" />
- 工厂类StaticFactory
package com.wei.factory;
import com.wei.service.UserService;
import com.wei.service.impl.UserServiceImpl;
public class StaticFactory {
public static UserService getAccountService(){
return new UserServiceImpl();
}
}
bean 的中作用范围调整
- scope属性:用于指定bean的作用范围
- singleton 单例
- prototype 多例
- request 作用于web应用的请求范围
- session 作用于web应用的会话范围
- global-session:作用于集群环境的会话范围,不是集群环境时,它就是session
bean对象的声明周期
- 单例对象的声明周期与容器相同
- 多例对象在对象使用时,spring创建。
- 多例对象在使用就一直存在。
- 多例对象长时间不用,且没有别的对象引用时,由java的垃圾回收机制GC回收.
Spring的依赖注入
- 依赖注入 :Dependency Injection
- IOC的作用:降低程序间的耦合(依赖关系)
- 依赖关系的管理:以后都交给spring来维护。
- 能注入的数据:基本类型和String、其他bean类型(XML或注解配置过的bean)、复杂类型/集合类型
- 注入的方式:构造函数、set方法、注解
- 构造函数注入
- 当我们要用某些值注入时,用构造函数最好。
public class UserServiceImpl implements UserService {
private String name;
private Integer age;
private Date birthday;
public UserServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
<bean id="userService" class="com.wei.service.impl.UserServiceImpl" >
<!-- name 最好用,type,index 可选 -->
<constructor-arg name="name" value="wang" />
<constructor-arg name="age" value="12" />
<constructor-arg name="birthday" ref="date" />
</bean>
<bean id="date" class="java.util.Date" ></bean>
- set方法注入
- 优势 :创建对象没有明确的限制,可以直接使用默认构造函数
- 弊端:如果某个成员必须有值,则获取对象是有可能set方法没有执行。
<bean id="userService" class="com.wei.service.impl.UserServiceImpl" >
<property name="age" value="2" />
<property name="birthday" ref="date" />
<property name="name" value="wang.." />
</bean>
- 注解注入
- 具体注解的方式,请看标题:基于注解的IOC配置
- 测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = applicationContext.getBean("userService",UserService.class);
- 复杂类型 / 集合类型的数据注入
简单的数据类型和其他的Bean类型的数据已经配置过了。接下来注入一下复杂类型 / 集合类型的数据。
用于list结构:list,array,set . 用于map结构:map ,properties
<bean id="userDao" class="com.wei.dao.impl.UserDaoImpl" >
<property name="myString" >
<list>
<value>AAA</value>
</list>
</property>
<property name="myList" >
<list>
<value>BBB</value>
</list>
</property>
<property name="mySet" >
<list>
<value>ccc</value>
</list>
</property>
<property name="myMap" >
<map>
<entry key="key1" value="value1"/>
</map>
</property>
<property name="myProps" >
<map>
<entry key="key2" value="value2"/>
</map>
</property>
</bean>
public class UserDaoImpl implements UserDao {
private String[] myString;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
//setting 省略
//test
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = applicationContext.getBean("userDao",UserDao.class);
userDao.saveUser();
- 注入结果
基于注解的IOC配置
用于创建对象的–bean功能
- @Component :不属于任何一层时使用。
- @Controller : 表现层
- @Service :业务层
- @Repository :持久层
- 作用:用于把当前类对象存入spring容器中。
- 属性:value:用于指定bean的id.(不写时,默认值是当前类名,且首字母改小写。)
- 以上三个注解,他们的作用和属性与Component是一样的。但是他们是spring为我们提供明确的三层使用的注解,使三层对象更加清晰
- 需要开启扫描。对应的注解为@ComponentScan
<?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"
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名称空间和约束中>-->
<context:component-scan base-package="com.wei" />
//类
@Component("userService")
public class UserServiceImpl implements UserService {
...
}
//测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.saveUser();
用于注入数据的–bean标签中的property
- 以下三种注入,只能注入其他的bean类型,不能是基本类型和string的注解实现
- 另外,集合类型的注入只能通过xml实现
- @Autowired
- 作用:自动按照类型注入。只要容器种有唯一的一个bean对象类型和要注入的变量类型匹配,就可以成功注入。
- 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
- 如果有多个可以匹配的:先类型匹配,如果有多个,再用变量名去匹配id。
- 出现的位置:可以是变量上,也可以是方法上。
- 细节:在使用注解注入时,set方法就不是必须的。
- 如先按照类型匹配,然后再按照变量名匹配。
- @Qualifier 与@Autowired 配合使用才行。
- 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。(但是给方法参数注入时可以单独使用。)
- 属性:value:用于指定注入bean的id。
- @Resource , 只需一个注解即可指定注入的bean的id。
- 作用:直接按照bean的id注入。它可以独立使用
- 属性:name:用于指定bean的id
- @Value
- 作用:用于注入基本类型和String类型的数据。
- 属性 value:用于指定数据的值,它可以使用spring中的spEl(Spring El表达式) spEL: ${表达式}
- @Autowired 代码
- 此例,通过类型查找,UserDao类型对应有2个,匹配不上。
- 再通过名称查找,没有与 变量名userDao 一样的component,所以会报错。
No qualifying bean of type 'com.wei.dao.UserDao' available: expected single matching bean but found 2: userDao1,userDao2
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
}
@Repository("userDao1")
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存了用户");
}
}
@Repository("userDao2")
public class UserDaoImpl2 implements UserDao {
@Override
public void saveUser() {
System.out.println("保存了用户222");
}
}
//测试
public class Client {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.saveUser();
}
}
- @Qualifier 代码
- 配合@Qualifier便可以找到。但是不够好。
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("userDao1")
private UserDao userDao;
- @Resource 代码
@Service("userService")
public class UserServiceImpl implements UserService {
//效果与Autowired + Qualifier 一样。
@Resource(name = "userDao1")
private UserDao userDao;
}
- @Value 代码
@Service("userService")
public class UserServiceImpl implements UserService {
@Value("value annotation setting value")
private String value;
public String getValue() {
return value;
}
}
用于改变作用范围–bean中的scope
- @Scope:用于指定bean的作用范围
- 属性 value:指定范围的取值 默认单例 singleton,可选多例 prototype
@Service("userService")
@Scope("prototype") //spring默认单例,这里设置为多例
public class UserServiceImpl implements UserService {
...
}
和声明周期相关–bean中使用init-method,destroy-method (了解)
- 指定初始化方法和销毁方法
@Service("userService")
//@Scope("prototype") 多例情况下,bean的死亡是由java回收机制GC负责。
public class UserServiceImpl implements UserService {
@Override
public void saveUser() {
userDao.saveUser();
}
// 指定初始化方法
@PostConstruct
public void init(){
System.out.println("init .....");
}
//指定销毁方法
@PreDestroy
public void destroy(){
System.out.println("destroy .....");
}
}
//测试代码
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.saveUser();
applicationContext.close();
}
- 结果
配置类相关注解
- @Configuration 作用:指定当前类是一个配置类。
- 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,可不写。
- @componentScan 作用:用于通过注解指定spring在创建容器时哟啊扫描的包。
- 属性value与basePackages 是一样的。因为底层有使用别名。
- @import 作用:用于导入其他的配置类。
- 使用import注解的为主配置类,导入的为子配置类。
- @Bean 作用:把当前方法的返回值作为bean对象,存入spring的ioc容器中。
- 属性name:用于指定bena的id。属性默认值为当前方法的名称。
- 细节: 当我们使用注解配置方法时,如果方法有参数,spring会去容器里找有没有可用的bean对象。
- 查找的方式和autowired注解的作用是一样。先类型。先名字。
- PropertySource 作用:用于指定Properties文件的路径
- 属性value : 指定文件的名称和路径
- 关键字:classpath 表示为类路径下
// 主配置类。
@Configuration
@ComponentScan(basePackages = "com.wei")
@Import({JdbcConfig.class})
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
//子配置类。
@Configuration
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 用于创建一个QueryRunner对象
*/
@Bean(name = "runner")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
@Bean("dataSource")
@Scope("prototype")
public DataSource createDataSource(){
ComboPooledDataSource ds = new ComboPooledDataSource();
try {
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
return ds;
}
}
补充注解
- Qualifier 的另一个用法和Junit测试注解
- @Qualifier 的另一种用法。指定多个实现类中的一个。
@Configuration
public class JdbcConfig {
@Bean(name = "runner")
public QueryRunner createQueryRunner(@Qualifier("ds1") DataSource dataSource){
return new QueryRunner(dataSource);
}
@Bean("ds1")
public DataSource createDataSource1(){
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver1);
ds.setJdbcUrl(url1);
ds.setUser(username1);
ds.setPassword(password1);
return ds;
}
@Bean("ds2")
public DataSource createDataSource2(){
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver2);
ds.setJdbcUrl(url2);
ds.setUser(username2);
ds.setPassword(password2);
return ds;
}
- Spring整合junit的配置
- junit不知道我们使用了spring,所以也就不会读取配置,创建spring核心容器。
- 需要导入spring 整合junit的坐标。
- 使用@Runwith 注解替换main方法
- 告知spring的运行期,spring和ioc的创建是基于xml还是注解的,并指定位置。
locations:指定xml文件的位置,加上classpath关键字,表示在类路径下。
classes:指定注解类所在的位置 - 当我们使用spring 5.x版本时,junit,需要4.1.2以上,不会无法运行。
<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>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( classes = SpringConfiguration.class)
//@ContextConfiguration( locations = "classpath:bean.xml")
public class Client {
@Autowired
private AccountService accountService;
@Test
public void findAll(){
List<Account> all = accountService.findAll();
System.out.println(all);
}
注解和XML配置如何选择?
- 取决于公司已经建成的项目以哪种方式进行开发。
- 建议混合使用。
- 如果是别人写好的类,建议使用XML
- 如果是自己写的类,建议使用注解
动态代理
- 特点:字节码随用随创建,随用随加载。
- 作用:不修改源码的基础上对方法增强。
- 分类:基于接口的动态代理和基于子类的动态代理。
基于接口的动态代理
-
涉及的类:Proxy
-
提供者:JDK官方
-
如果创建代理对象? 使用Proxy类中的newProxyInstance方法。
-
创建代理对象的要求:被代理类最少实现一个接口,如果没有,则不能使用。
- newProxyInstance方法的参数:
- ClassLoader :它是用于加载代理对象字节码的。是被代理对象使用相同的类加载器。固定写法(代理谁,就写谁的ClassLoader)
- Class[] :字节码数组。它是用于让代理对象和被代理对象有相同的方法。(两个实现同一个接口,它们就有共同的方法)。固定写法。
- InvocationHandler :用于提供增强的代码。它是让我们写如何代理。一般都是写一个InvocationHandler 接口的实现类。此方法的实现类都是谁用谁写的。
//实现IProducer接口的被代理对象Producer
public class Producer implements IProducer {
@Override
public void saleService(Float money) {
System.out.println("销售产品,赚了"+money);
}
@Override
public void afterService(Float money) {
System.out.println("提供售后,赚了"+money);
}
}
//模拟
public class Client {
public static void main(String[] args) {
Producer producer = new Producer();
//代理前
producer.saleService(1000f);
producer.afterService(100f);
//代理对象producerProxy ,接口IProducer
IProducer producerProxy = (IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
/**
* 作用:执行被代理对象的任何借口方法都会经过该方法。
* 方法参数的含义。
* @param proxy 代理对象的引用。
* @param method 当前执行的方法。(被代理对象的方法。也可能没有代理。)
* @param args 当前执行的方法所需的参数。
* @return 和被代理对象有相同的返回值。
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Float money = (Float) args[0];
if ("saleService".equals(method.getName())){
return method.invoke(producer,money*0.8f);
}
if("afterService".equals(method.getName())){
return method.invoke(producer,money+1.0f);
}
return method.invoke(producer,args);
}
});
System.out.println("动态代理后");
producerProxy.saleService(1000f);
producerProxy.afterService(100f);
}
}
基于子类的动态代理
- 需要第三方jar 的支持
- 同时需要一个依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
asm可自动导入。
-
涉及的类:Enhancer
-
提供者:第三方cglib库
-
如果创建代理对象? 使用Enhancer类中的create方法。
-
创建代理对象的要求:被代理类不能是最终类。
- create方法的参数:
- Class:指定被代理对象的字节码。
- callback:用于提供增强的代码。它是让我们写如何代理。一般都是写一个接口的实现类。此方法的实现类都是谁用谁写的。
- 我们一般写的都是该接口的子接口实现类,MethodInterceptor
//没有接口,通过基于子类的动态代理
public class Producer{
public void saleService(Float money) {
System.out.println("销售产品,赚了"+money);
}
public void afterService(Float money) {
System.out.println("提供售后,赚了"+money);
}
}
public class Client {
public static void main(String[] args) {
Producer producer = new Producer();
Producer cglibproducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 被代理对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的。
* @param methodProxy 当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//被代理对象的方法。
Float money = (Float) args[0];
if ("saleService".equals(method.getName())) {
return method.invoke(producer, money * 0.8f);
}
if ("afterService".equals(method.getName())) {
return method.invoke(producer, money + 1.0f);
}
return method.invoke(producer, args);
}
});
cglibproducer.saleService(1000f);
cglibproducer.afterService(1000f);
}
}
APO 面向切面编程
- Aspect Oriented Programming : 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- 低耦合,高重用。
- 作用:在程序运行期间,不修改源码对已有的方法进行增强。
- 优势:减少重复代理、提高发开效率和维护方便。
- 实现技术:使用动态代理技术。
AOP相关术语
- 1、Joinpoint(连接点):指那些被拦截到的点。在spring中,这些点指得是方法,因为spring只支持方法类型的连接点。
- 2、Cutpoint(切入点):指我们要对哪些连接点进行拦截的定义(进行增强的方法)。
- 3、Advice(通知/增强):拦截到切入点之后要做的事情就是通知。通知分为前置、后置、异常、最终和环绕。
环绕通知:整个invoke方法在执行就是环绕通知。在环绕通知中有明确的切入点方法调用,即被增强的方法。
- 4、Introduction(引介):一种特殊的通知,在不修改代码的前提下,引介可以再运行期为类动态添加一些方法或Field。、
- 5、Target(目标对象) 即被代理对象。
- 6、Weaving(织入):把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译器织入和类装载期织入。(加入事务的过程叫织入。(invoke内容))
- 7、Proxy(代理):一个类被AOP织入增强后,就产生了一个结果代理类。
- 8、Aspect(切面):是切入点和通知(或引介)的结果。
基于XML的AOP
- 1、先把需要用到的bean交给spring管理。service层、Logger
- 2、使用aop:config标签,表名开始AOP的配置
- 3、使用aop:aspect标签表名配置切面
id属性:切面的唯一标识
ref属性:指定通知之类bean的id - 4、在aop:aspect标签的内部使用对应标签来配置通知的类型。
实例让printLog方法在切入点之前执行,即前置通知。
aop:before 表示配置前置通知。
method属性:用于指定Logger类中哪个方法是前置通知。
pointcut-ref / pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法进行增强。
- 5、切入点表达式
- 关键字:execution
- 表达式: 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.wei.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符表示任意返回值。
包名可以使用通配符,表示任意包,几个*. 就是几级包。
* *..accountServiceImpl.saveAccount(..)
类名和方法名可以使用通配符。
参数列表:可以直接简写数据类型。一个*就表示任意类,但是必须有一个。
全通配写法:
* *..*.*(..)
- 实际开发中切入点表达式的通常写法:切到业务层实现类下的所有方法。
* com.wei.service.impl.*.*(..)
- pow.xml
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--一定要有aspectjweaver,不然必错。-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
<?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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring的IOC,把service配置进去-->
<bean id="accountService" class="com.wei.service.impl.AccountServiceImpl" ></bean>
<!--配置Logger类-->
<bean id="logger" class="com.wei.utils.Logger" ></bean>
<!--配置AOP-->
<aop:config >
<!--配置切入点表达式 id属性用于执行表达式的唯一标识
此标签可以写在apo:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect标签外部,此时可以供所有切面使用。
-->
<aop:pointcut id="servicePointCut" expression="execution(* com.wei.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,并且建立通知的方法和切入点方法的关联。-->
<aop:before method="printLog" pointcut-ref="servicePointCut" />
</aop:aspect>
</aop:config>
</beans>
- 四种通知类型
<aop:config >
<!--配置切入点表达式 id属性用于执行表达式的唯一标识 -->
<aop:pointcut id="servicePointCut" expression="execution(* com.wei.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--后置和异常之间只会执行一个。要么正常执行,要么出现异常。-->
<!--配置前置通知:在切入点方法执行之前。-->
<aop:before method="beforePrintLog" pointcut-ref="servicePointCut" />
<!--配置后置通知:在切入点方法执行之后。-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="servicePointCut" />
<!--配置异常通知:在切入点方法出现异常之后。-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="servicePointCut" />
<!--配置最终通知:无论切入点方法是否正常执行,它都会在其后执行。-->
<aop:after method="afterPrintLog" pointcut-ref="servicePointCut" />
</aop:aspect>
</aop:config>
- 运行结果
- 正常运行
- 出现异常
- 环绕通知
问题:当我们配置了环绕通知后,切入点方法没有执行,而通知方法执行了
分析:动态代理的环绕通知有明确的切入点方法调用,而我们的代码没有。
解决:spring提供了一个接口 ProceedingJoinPoint.该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
spring中的环绕通知。它是spring为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
<!--配置AOP-->
<aop:config >
<!--配置切入点方法 -->
<aop:pointcut id="servicePointCut" expression="execution(* com.wei.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置环绕通知-->
<aop:around method="aroundPrintLog" pointcut-ref="servicePointCut"></aop:around>
</aop:aspect>
</aop:config>
package com.wei.utils;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 用于记录日志的工具类,它里面提供了公共的代码。
*/
public class Logger {
/**
* 用于打印日志:计划让其在切入点方法执行之前执行。切入点方法就是业务层方法。
*/
public void beforePrintLog(){System.out.println("前置。Logger类的printLog方法开始记录日志了。beforePrintLog");}
public void afterReturningPrintLog(){System.out.println("后置。Logger类的printLog方法开始记录日志了。afterReturningPrintLog");}
public void afterThrowingPrintLog(){System.out.println("异常。Logger类的printLog方法开始记录日志了。afterThrowingPrintLog");}
public void afterPrintLog(){System.out.println("最终。Logger类的printLog方法开始记录日志了。afterPrintLog");}
public Object aroundPrintLog(ProceedingJoinPoint point){
Object returnValue = null;
try {
Object[] args = point.getArgs();
//System.out.println("环绕通知!。Logger类的aroundPrintLog方法开始记录日志了。。前置");
returnValue = point.proceed(args);
//System.out.println("环绕通知!。Logger类的aroundPrintLog方法开始记录日志了2。。后置");
return returnValue;
} catch (Throwable throwable) {
//System.out.println("环绕通知!。Logger类的aroundPrintLog方法开始记录日志了3。。异常");
throw new RuntimeException(throwable);
}finally {
//System.out.println("环绕通知!。Logger类的aroundPrintLog方法开始记录日志了4。。最终");
}
}
}
基于注解的AOP
- 前置、后置、异常、最终通知的使用,执行顺序有问题。
- 推荐使用环绕通知。不会出现执行顺序问题。
- 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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.wei" ></context:component-scan>
<!--开始注解aop支持-->
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
</beans>
此处没有实现全注解。
可以再主配置类上添加注解实现,全注解配置。(然后读取时就可以不用读bean.xml,而是读主配置类.class)
扫描可以通过@componentScan实现。
开启注解支持可以通过@EnableAspectJAutoProxy 实现。
- 接口实现类
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
//省略业务代码
}
- Logger :工具类,写增强代码(公共代码)
@Component("logger")
//当前类是一个切面类。
@Aspect
public class Logger {
@Pointcut("execution(* com.wei.service.impl.*.*(..))")
private void pt1(){}
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置。Logger类的printLog方法开始记录日志了。beforePrintLog");
}
@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置。Logger类的printLog方法开始记录日志了。afterReturningPrintLog");
}
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常。Logger类的printLog方法开始记录日志了。afterThrowingPrintLog");
}
@After("pt1()")
public void afterPrintLog(){
System.out.println("最终。Logger类的printLog方法开始记录日志了。afterPrintLog");
}
//@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint point){
Object returnValue = null;
try {
Object[] args = point.getArgs();
System.out.println("环绕通知!。Logger类的aroundPrintLog方法开始记录日志了。。前置");
returnValue = point.proceed(args);
System.out.println("环绕通知!。Logger类的aroundPrintLog方法开始记录日志了2。。后置");
return returnValue;
} catch (Throwable throwable) {
System.out.println("环绕通知!。Logger类的aroundPrintLog方法开始记录日志了3。。异常");
throw new RuntimeException(throwable);
}finally {
System.out.println("环绕通知!。Logger类的aroundPrintLog方法开始记录日志了4。。最终");
}
}
}
- 环绕通知结果
java动态代理
跳转:https://blog.csdn.net/qq_30081043/article/details/107632673
JdbcDaoSupport的使用和Dao的两种编写方式
跳转:https://blog.csdn.net/qq_30081043/article/details/107645785
Spring的事务控制
Spring中事务控制的 API
Spring基于XML的声明式事务控制(推荐)
- 这里不关注业务,只关注配置和是否实现了事务控制。
public class AccountServiceImpl implements IAccountService{
...
@Override
public void transfer(String sourceName, String targetName, Float money) {
//转出账户
accountDao.updateAccount(source);
//出现异常
int i=1/0;
//转入账户
accountDao.updateAccount(target);
}
}
spring中基于XML的声明式事务控制配置步骤
1、配置事务管理器 DataSourceTransactionManager,并注入数据源 DriverManagerDataSourc
2、配置事务的通知
此时我们需要导入事务的约束 (tx名称空间和约束),同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性
是在事务的通知tx:advice标签的内部
- 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: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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.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">
<!-- 配置业务层-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<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://localhost:3306/spring"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置事务管理器,并注入数据源 DriverManagerDataSource -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--事务通知指定切入点表达式-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
- pow.xml 关键:context,jdbc,tx,aspectjweaver
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
Spring基于注解的声明式事务控制
@Service("accountService")
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)//只读型事务的配置
public class AccountServiceImpl implements IAccountService{
//需要的是读写型事务配置
@Transactional(propagation= Propagation.REQUIRED,readOnly=false)
public void transfer(String sourceName, String targetName, Float money) {
...
}
<?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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.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.itheima" />
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</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://localhost:3306/spring"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- spring中基于注解的配置的声明式事务控制配置步骤
1.配置事务管理器,并注入数据源 DriverManagerDataSource
2.开启spring对注解事务的支持
3.在需要事务支持的地方使用@Transactional注解
-->
<!--1.配置事务管理器,并注入数据源 DriverManagerDataSource-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--2.开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager" ></tx:annotation-driven>
</beans>
Spring基于全注解的声明式事务控制
- 参考项目:day04_eesy_07anno_tx_withoutxml
- 来源:黑马57
- @Transactional 是声明式事务管理
1 .添加位置
1)接口实现类或接口实现方法上,而不是接口类中。
2)访问权限:public 的方法才起作用。@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。
系统设计:将标签放置在需要进行事务管理的方法上,而不是放在所有接口实现类上:只读的接口就不需要事务管理,由于配置了@Transactional就需要AOP拦截及事务的处理,可能影响系统性能。
2. @Transactional注解
@Transactional 实质是使用了 JDBC 的事务来进行事务控制的
@Transactional 基于 Spring 的动态代理的机制
更多Transactional 的用法参考:https://blog.csdn.net/jiangyu1013/article/details/84397366
- @EnableTransactionManagement
表示开启对事务的控制的注解支持。
- 事务管理器配置 TransactionConfig
package config;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象
*/
@Bean(name = "transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
使用Jdbc和注入数据源,另需@PropertySource 导入properties
package config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
/**
* 和连接数据库相关的配置类
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建jdbcTemplate
*/
@Bean(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource (){
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(driver);
driverManagerDataSource.setUrl(url);
driverManagerDataSource.setUsername(username);
driverManagerDataSource.setPassword(password);
return driverManagerDataSource;
}
}
- test类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void testTransfer(){
accountService.transfer("aaa","bbb",100f);
}
}
Spring编程式事务控制
- 配置事务管理器
- 配置事务模板对象 Transactiontemplate
- 会重新出现重复的代码。背离了初心。