赞
踩
安排:
day1: spring框架的概述以及spring中基于XML的IOC(反转控制)配置
day2:Spring中基于注解的IOC和IOC案例
day3:spring中的AOP和基于XML以及注解的AOP(面向切面编程)配置
day4:spring中的JDBC Temlate以及Spring事物控制
1.1spring的优势:方便解耦,简化开发
AOP编程的支持
声明式事务的支持
方便程序的测试
方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、 Hessian、Quartz等)的直接支持。
降低JavaEE API的使用难度
Java源码是经典学习范例
划分模块的一个准则就是高内聚低耦合
耦合的原则:耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
1.2 spring 是什么?
1.3 spring 的两大核心
core container是spring的核心之一,spring的任何其他部分运行都离不开它。
工厂模式创建对象的过程: 1. 用反射的方式创建对象 bean = Class.forName(beanPath).newInstance();
有能反射的全限定类名accountService = com.itheima.service.impl.AccountImpl
accountDao = com.itheima.service.dao.impl.AccountDaoImpl
通过读取配置文件的方式来反射从而得到
Sverlet是一个单例对象
在此次的例子中,bean = Class.forName(beanPath).newInstance(); //每次都会调用默认构造函数 创建对象
每次都会创建新的对象,而旧的被垃圾回收机制默认回收了。这就带来一个问题,多例对象运行效率没有单例对象高,因此我们使用容器解决这个问题,使它变成个单例的。
``
package com.itheima.service.factory; import java.io.InputStream; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; /* * 一个创建Bean对象工厂 * * Bean在计算机语言中,有可重用组件的含义 * JavaBean:用java语言编写的可重用组件 * javabean > 实体类 * * 一个创建Bean对象工厂,它是创建我们的service和dao对象。 * 第一个:需要配置文件来配置我们的service和dao * 配置的内容:唯一标志的名字=全限定类名(key=value) * 第二个/ *:通过读取配置文件中的内容,反射创建对象 * * */ public class BeanFactory { private static Properties prop; //定义一个map,用来存放我们要创建的对象 private static Map<String,Object> beans; //使用静态代码块为prop赋值,编译器就开始执行 static { try { //实例化prop prop = new Properties(); //获取properties的流对象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); prop.load(in); //实例化beans beans = new HashMap<String, Object>(); Enumeration keys = prop.keys(); //遍历枚举 while(keys.hasMoreElements()){ //取出每个key值 String key = keys.nextElement().toString(); //根据key值得到路径 String beanPath = prop.getProperty(key); //利用的到的路径,创建反射对象 Object value = Class.forName(beanPath).newInstance(); //把得到的key,value存到容器中 beans.put(key,value); } }catch(Exception e){ throw new ExceptionInInitializerError("初始化对象错误"); } } /** * 根据bean的名称获取对象 * @param beanName * @return */ public static Object getBean(String beanName) { return beans.get(beanName); } /** *根据Bean的名称来获取bean对象 * @param beanNamme * @return * public static Object getBean(String beanNamme){ Object bean = null; try{ String beanPath = prop.getProperty(beanNamme); bean = Class.forName(beanPath).newInstance(); //每次都会调用默认构造函数 创建对象 }catch(Exception e){ e.printStackTrace(); } return bean; }*/ }
``
spring中基于XML的环境搭建
控制反转:(Inversion of Control)把创建对象的权利交给框架,是框架的重要特征。它包括依赖注入和依赖查找。
ioc的作用,降低程序间的耦合
具体解耦过程入下面步骤所示
<?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>com.itheima</groupId> <artifactId>day01_eesy_spring</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.9.RELEASE</version> </dependency> </dependencies> </project>
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把对象的创建交给spring来管理-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
<bean id="accountService" class="com.itheima.AccountService.impl.AccountImpl"></bean>
</beans>
2)解析spring对bean的管理细节
1.创建bean的三种方式
第一种:使用默认构造函数创建在spring的配置文件中使用bean标签,配以属性id和class后没有其 他属性时采用这种方式创建,如果此类中没有默认构造函数,则对象无法被创建。
第二种:使用普通工厂中的方法创建,使用某个类中的方法创建对象,并存入spring容器(当类存 在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
<bean id="accountService" factory-bean="instanceFactory" factory- method="getAccountService" >
</bean>
第三种:使用某个类中的静态方法创建
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService" >
</bean>
3)Spring中的依赖注入
注入数据: 可以注入三种数据:第一种: 基本类型和String类型
第二种:其他bean类型(在配置文件中或者注解配置过的bean)
第三种:复杂类型/集合类型
注入的方式: ①默认构造函数注入 :
使用的标签:constructor-arg
属性标签中的属性:
type:用于指定构造函数中的某个或者某些参数的数据类型
index:用于指定构造函数的位置,从0开始
name:用于指定构造函数中的参数的名字,更为常用
value:为基本数据类型和String类型注入具体的内容
ref:用于指定其他的bean类型,它指定的是spring容器中出现过的bean对象
优势:必须将构造函数中所有的参数进行内容注入
<bean id="accountService" class="com.itheima.AccountService.impl.AccountImpl">
<constructor-arg name="name" value="Bazahai"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="tommoron"></constructor-arg>
</bean>
<bean id="tommoron" class="java.util.Date"></bean>
②用set方法
使用的标签:property
不可以提供构造函数,因为构造函数注入和set方法注入是不兼容的
标签中的属性:
name:用于注入时所调用的方法名
value:为基本数据类型和String类型注入具体的内容
ref:用于指定其他的bean类型,它指定的是spring容器中出现过的bean对象
优势:可以为我们想要注入的数据注入内容,更加灵活
<bean id="accountService2" class="com.itheima.AccountService.impl.AccountImpl2">
<property name="userName" value="Bazahai"></property>
<property name="age" value="17"></property>
<property name="birthday" ref="tommoron"></property>
</bean>
<bean id="tommoron" class="java.util.Date"></bean>
3)使用注解提供/复杂类型的注入
用于给List集合注入的标签:
list、 array、 set
用于给map集合注入的标签:
map、 props
结构相同,标签可以互换
<bean id="accountService3" class="com.itheima.AccountService.impl.AccountImpl3"> <property name="myList"> <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> <property name="myStr"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <property name="mySet"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> <property name="myMap"> <map> <entry key="TestA" value="AAA"></entry> <entry key="TestB" value="BBB"></entry> <entry key="TestC" value="CCC"></entry> </map> </property> </bean> <bean id="tommoron" class="java.util.Date"></bean>
2.bean对象的生命周期
单例对象,和容器同生共死
多例对象,出生:当我们使用对象时
活着:对象在使用过程中一直活着
死亡:当对象长时间不用时,也没有别的对象引用时,被java垃圾回收机制回收
3.bean对象的作用范围
bean标签的scope属性,用于指定bean的作用范围。
singleton 单例的,默认值
prototype 多例的
request 作用于web应用的请求范围
session 作用于web应用的的会话范围
global-session 作用于集群环境的会话范围,区别见下图,当不是集群时,作用等同于 session
package com.itheima.ui; import com.itheima.AccountService.IAccountService; import com.itheima.dao.AccountDao; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /* * 模拟一个表现层,调用业务层 * */ public class Client { /** *获取spring的Ioc核心容器,并根据id获取对象 * ApplicationContextde三个常用实现类 * ClassPathXmlApplicationContext 他可以加载类路径下的配置文件,配置文件必须在类路径下,不在的话加载不了(更常用) * FlieSystemXmlApplicationContext 他可以加载磁盘任意路径下的配置文件(必须有访问权限) * AnnotationConfigApplicationContext 他是用于读取注解创建容器的 * *核心容器的两个接口引发的问题: * ApplicationContext: * 它在构建核心容器时,创建的方式是立即加载的方式,当配置文件被读取后就会立即创建对象 * BeanFactory: * 它构建核心容器时采用延迟加载的方式,也就是说,什么时候根据id获取对象了,什么时候才创建对象 * * @param args */ public static void main(String[] args) { // IAccountService as = new AccountImpl(); //获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根据id获取bean对象 IAccountService as = (IAccountService)ac.getBean("accountService"); AccountDao ado = (AccountDao) ac.getBean("accountDao"); // AccountDao ado = ac.getBean("accountDao","accountDao.class"); System.out.println(as); System.out.println(ado); } }
java的三层架构:
SSH:
Struts(表示层)+Spring(业务层)+Hibernate(持久层)
Struts是一个表示层框架,主要作用是界面展示,接收请求,分发请求
Hibernate:Hibernate是一个持久层框架,它只负责与关系数据库的操作。
Spring:Spring是一个业务层框架,是一个整合的框架,能够很好地黏合表示层与持久层。
编译期错误
程序开发过程的各个阶段都可能发生错误,可以将程序设计中的错误分成五类:
1)编译期错误
2)连接错误
3)运行期错误
4)逻辑性错误
5)警告性错误
排错是非常困难的,有可能花费很长的时间。程序设计的目标应该是避免出现太多的问题。对减少 排错能有所帮助的技术包括:好的设计、好的风格、边界条件测试、合理性检查、限制全局数据等等
Ioc到底干了一件什么事?
层层封装,形成模块化,良好代码环境,便于维护。维护不用修改源码,只操作bean就可。
当我们不想new一个对象,在获得容器的控制权后,利用镜像的方式创建操作的类对象,调用类方法。
1、spring中ioc的常用注解
2、案例使用xml方式和注解方式实现单表的CRUD操作
持久层技术选择:dbutils
3、改造基于注解的ioc案例,使用纯注解的方式实现
spring的一些新注解使用
4、spring和Junit整合
当用于创建对象时,@Component @Service @Repository @Contoller的作用就和在XML配置文件中编写一个标签实现的功能是一样的。当使用注解注入时需要在xml文件中告知要扫描的包 “<context:component-scan base-package=“com.itheima”></context:component-scan>”
①@Component
②@Service:一般用在业务层
③@Repository:一般用在持久层
④@Contoller:一般用在表现层
以上三个注解他们的作用和属性与Component是一模一样。他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰。
用于注入数据时,@Autowierd @Qualifiy @Resource的作用就和在xml配置文件中的bean标签中写一个标签的作用是一样的。
⑤@Autowierd
⑥@Qualifiy
作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以(稍后我们讲)
属性:value:用于指定注入bean的id。
⑦@Resource
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过XML来实现。
/** * 账户的业务层实现类 * * 曾经XML的配置: * <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" * scope="" init-method="" destroy-method=""> * <property name="" value="" || ref=""></property> * </bean> * * Value * 作用:用于注入基本类型和String类型的数据 * 属性: * value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式) * SpEL的写法:${表达式} * * 用于改变作用范围的 * 他们的作用就和在bean标签中使用scope属性实现的功能是一样的 * Scope * 作用:用于指定bean的作用范围 * 属性: * value:指定范围的取值。常用取值:singleton prototype * * 和生命周期相关的(了解) * 他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的 * PreDestroy * 作用:用于指定销毁方法 * PostConstruct * 作用:用于指定初始化方法 */ //@Service("accountService") //@Scope("prototype") public class AccountServiceImpl implements IAccountService { // @Autowired // @Qualifier("accountDao1") // @Resource(name = "accountDao2") @Autowired private IAccountDao accountDao=null; @PostConstruct public void init(){ System.out.println("初始化方法执行了"); } @PreDestroy public void destroy(){ System.out.println("销毁方法执行了"); } public void saveAccount(){ accountDao.saveAccount(); } }
①账户的持久层实现类(接口略)
public class AccountDaoImpl implements AccountDao { @Autowireds private QueryRunner runner; /* public void setRunner(QueryRunner runner) 当我们用注解注入的时候,set方法就不是必须的了 {this.runner=runner;} */ @Override public List<Account> findAllAccount() { try{ return runner.query("select * from account",new BeanListHandler<Account>(Account.class)); }catch (Exception e) { throw new RuntimeException(e); } } @Override public Account findAccountById(Integer accountId) { try{ return runner.query("select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void saveAccount(Account account) { try{ runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney()); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void updateAccount(Account account) { try{ runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void deleteAccount(Integer accountId) { try{ runner.update("delete from account where id=?",accountId); }catch (Exception e) { throw new RuntimeException(e); } } }
②账户的业务层实现类(接口略)
public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Override public List<Account> findAllAccount() { return accountDao.findAllAccount(); } @Override public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } @Override public void saveAccount(Account account) { accountDao.saveAccount(account); } @Override public void updateAccount(Account account) { accountDao.updateAccount(account); } @Override public void deleteAccount(Integer acccountId) { accountDao.deleteAccount(acccountId); } }
③账户的实体类
public class Account implements Serializable { private Integer id; private String name; private Float money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Float getMoney() { return money; } public void setMoney(Float money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
①数据配置: 数据最终注入AccountService中,因此从终结点出发,一步一步将剩下的配置齐
<!--配置AccountService--> <bean id="accountService" class="AccountService的类路径"> <!--数据的注入,accountService对象的使用必须建立在accountDao对象的建立之上--> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置AccountDao--> <bean id="accountDao" class="AccountDao的类路径"> <!--注入数据,AccountDao的使用必须建立在QueryRunner对象建立的基础之上--> <property name="runner" ref="runner1"></property> </bean> <!--配置QueryRunner--> <bean id="runner1" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入数据源,需要数据源对象--> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> <!--配置dataSource--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <properties name="driverClass" value="com.mysql.jdbc.Driver"></properties> <properties name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></properties> <properties name="user" value="root"></properties> <properties name="password" value="1234"></properties> </bean> <!-- 当使用注解注入时需要告知spring在创建容器时要扫描的包 --> <!--<context:component-scan base-package="com.itheima"></context:component-scan> 并在需要注入的类中使用@Component @Service @Repository @Contoller 等 -->
②测试类的编写
public class AccountServiceTest{
@Test
public void testFindAll() {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
AccountServiceImpl as = ac.getBean("accountService", AccountServiceImpl.class);
//3.执行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
}
/** * 添加注解Configuration后这个类就变成了配置类,它的作用和bean.xml是一样的 * spring中的新注解 * @Configuration * 作用:指定当前类是一个注解类 * @ComponentScan * 作用:用于通过注解指定spring在创建容器时要扫描的包 * 属性:basePackages,value的作用是一样的,用于创建容器时指定扫描的包,我们使用此注解相当于在 * bean.xml中配置了 * <context:component-scan base-package="itheima"></context:component-scan> * 细节:当配置类作为annotationConfigApplicationContext对象创建参数时,该注解可以不写 * 例如:在下面的测试类中 ac是一个annotationConfigApplicationContext对象 * @Test * public void testFindAll(){ * ApplicationContext ac = new * annotationConfigApplicationContext(SpringConfigration.class); * ...... * } * ??? 当annotationConfigApplicationContext();()中没有指定配置类路径的情况下不能省略。 * @Bean * 作用:用于把当前方法的返回值作为bean对象传入到spring的ioc容器中 * 属性: * name:用于指定bean的id。当不写时,默认是当前的方法名。 * 细节: * * @Import * 作用:用于导入其它配置类 * 属性:value:用于指定其他配置类的字节码 * 当我们使用@import后,有Import注解类就是主或者父配置类。(推荐使用) * * * */ @Configuration @ConpnentScan(basePackages = {"com.itheima"}) //basePackages,和value属性都是数组,左侧为它的标准写法。 //@ConpnentScan(com.itheima) 当注解的属性有且只有一个值,那么可以写成左边的形式 @Import(JdbcConfig.class) public class SpringConfiguration{ @Bean(name="runner") public QueryRunner createRunner(DataSource dataSource) { return new QueryRunner(dataSource); } //上面方法的作用效果,在加@Bean后和下面注释的内容效果相同 /* <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入数据源,需要数据源对象--> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> /* * 创建数据源对象 * @return */ @Bean(name="dataSource") public QueryRunner createRunner(){ CombolPooledDataSource ds = CombolPooledDataSource(); ds.setDriverClass("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy"); ds.setUser("root"); ds.setPassWord("1234"); return ds; } //效果和下面注释代码相同 /* <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="1234"></property> </bean> */ }
①配置.properties文件 (jdbcConfig.properties)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
②编写配置类
/** * @PropertySource * 作用:用于指定 .properties 文件的位置 * 属性: * value:指定文件的名称和路径。关键字: classpath */ @PropertySource(classpath:jdbcConfig.properties) public class JdbcConfig{ @value("${jdbc.driver}") private String driver; @value("${jdbc.url}") private String url; @value("${jdbc.usename}") private String userName; @value("${jdbc.password}") private String password; @Bean(name="runner") @Scope("prototyype") public QueryRunner createRunner(DataSource dataSource) { return new QueryRunner(dataSource); } @Bean(name="dataSource") public QueryRunner createRunner(){ CombolPooledDataSource ds = CombolPooledDataSource(); ds.setDriverClass("driver"); ds.setJdbcUrl("url"); ds.setUser("userName"); ds.setPassWord("password"); return ds; } }
1、应用程序的入口
main方法
2、junit单元测试中,没有main方法也能执行
junit集成了一个main方法
该方法就会判断该类中有哪些方法使用了@Test注解
junit就会用有@Test注解的方法执行
3、junit不管我们是否使用了spring框架
在执行测试方法时junit不知道我们是否使用了spring‘框架,他也不会为我们读取配置文件/配置类,创建 spring核心容器
4、由以上三点可知,测试方法执行时,没有ioc容器的存在,所以就算使用了@Autowierd注解,也无法注入
/** * 使用Junit单元测试:测试我们的配置 * Spring整合junit的配置 * 1、导入spring整合junit的jar(坐标) * 2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的 * @Runwith * 3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置 * @ContextConfiguration * locations:指定xml文件的位置,加上classpath关键字,表示在类路径下 * classes:指定注解类所在地位置 * * 当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class AccountServiceTest { @Autowired private IAccountService as = null; @Test public void testFindAll() { //3.执行方法 List<Account> accounts = as.findAllAccount(); for(Account account : accounts){ System.out.println(account); } } @Test public void testFindOne() { //3.执行方法 Account account = as.findAccountById(1); System.out.println(account); } @Test public void testSave() { Account account = new Account(); account.setName("test anno"); account.setMoney(12345f); //3.执行方法 as.saveAccount(account); } @Test public void testUpdate() { //3.执行方法 Account account = as.findAccountById(4); account.setMoney(23456f); as.updateAccount(account); } @Test public void testDelete() { //3.执行方法 as.deleteAccount(4); } }
1)spring中的注解,有@Configration和@Import两种,前者支持的是并列关系的两个或两个以上 的注解类,使用后者则代表这是一个父配置类。
2)几种配置方式优缺点对比
xml:配置有一定的复杂
注解:并不一定省事
很多情况下用xml和注解混用的方式
从整个案例的过程可以看出一些端倪,业务逻辑层和持久层之间的联系。业务层调用持久层,对数据库进行操作。用maven工程创建整个案例的过程中,我们发现当创建了工程后,导入一堆坐标,即引如一堆可以使用的jar包,我们只可以使用但不可以修改这些包的内瓤。spring这个容器给了我们很多方便。容许我们导入,并且创建包内各类的对象,方便我们的使用。
动态代理:
②基于子类的动态类
基于接口的动态代理
如何创建代理对象:使用Proxy类中的newProxyInstance方法
1)涉及的类:jdk官方提供的 Proxy;
2)要求:被代理类必须至少实现一个接口,如果没有则不能使用
3)newProxyInstance方法的参数:
classLoader类加载器:它是用于加载代理对象字节码的,和被加载对象使用相同的类加载器
Class[] 字节码数组
InvocationHandler 用于提供增强的代码,通常情况下时匿名内部类,但不必须
基于子类的动态代理:
如何创建代理对象:使用Enhancer类中的create方法
1)涉及的类:Enhancer;提供者:cglib库
2)要求:被代理类不能是最终类
3)create()方法的参数:
class:字节码
类加载器:它是用于加载代理对象字节码的,和被加载对象使用相同的类加载器
Callback: 用于提供增强的代码,通常情况下时匿名内部类,但不必须。我们一般写的是该接口的子 接口实现类
<dependencies>
<dependency>
<groupId>cglib<groupId>
<artifactId>cglib<artifactId>
<version>版本号<version>
<dependency>
<dependencies>
/* * 基于接口的动态代理 */ IProducer proxyProducer = (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 { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(10000f); /* * 基于子类的动态代理 */ 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 { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); cglibProducer.saleProduct(12000f);
作用: 在程序运行期间,不修改源码对已有方法进行增强。
优势: 减少重复代码 提高开发效率 维护方便
AOP的实现方式:使用动态代理技术
<!--配置AOP--> <aop:config> <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容 此标签写在aop:aspect标签内部只能当前切面使用。 它还可以写在aop:aspect外面,此时就变成了所有切面可用 --> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知:在切入点方法执行之前执行--> <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before> <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个--> <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning> <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个--> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing> <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行--> <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after> <!-- 配置环绕通知 详细的注释请看Logger类中--> <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around> </aop:aspect> </aop:config>
* JoinPoint 连接点:所有的切入点都是连接点,反过来不成立
* pointcut 切入点:被增强的方法称为切入点
* Advice 通知:
思路分析:要将数据库的四个连接进行管理,要么全部发生,要么全不发生
1. 创建好转账方法后
2 创建一个连接的工具类,用于从数据源上获取连接,实现和和线程的
3 创建和事务管理相关的类,包含了开启、提交、回滚事务和释放连接
4 为了防止因为修改某些类中的方法而造成后期维护问题,因此使用动态代理。
*在业务中使用动态代理,一般是为了给需要实现的方法添加预处理或者添加后续操作,但是不干 预实现类的正常业务,把一些基本业务和主要的业务逻辑分离。我们一般所熟知的Spring的AOP 原理就是基于动态代理实现的。
连接池:把消耗时间获取连接的一部分放到应用加载一开始。在web应用中当我们启动tomCat加载应用时,我们创建一些连接从而在后续项目运行时不再和数据库进行连接,保证了我们使用connection时的效率。此时服务器也有一个池(线程池:它的特点是当TomCat启动时会初始化一大堆的线程,放到一个容器中,此后每当我们需要时他就会从线程池中取出一个线程供我们使用)
jdbc都是单表使用,不加多表是因为,多表操作是sql语句的职责。
jdbcTemplate:用来操作关系型数据库的。支持增删改。
位于:spring-jdbc-5.0.2.RELEASE.jar
还需要导入:spring-tx-5.0.2.RELEASE.jar,用于事务管理的
使用jdbc模板时需要配置模板
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
jdbcTemplate使用操作
//JdbcTemplate的CRUD操作 public class JdbcTemplateDemo2{ public static void main(String[] args) { //1.获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.获取对象 JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class); //3.执行操作 jt.execute("insert into account(name,money)values('ddd',2222)"); //保存操作 jt.update("insert into account(name,money)values(?,?)","fff",5000); //更新操作 jt.update("update account set money = money-? where id = ?",300,6); //删除操作 jt.update("delete from account where id = ?",6); //查询所有 // List<Account> accounts = jt.query("select * from account where money > ?",new AccountRowMapper(),1000f); //AccountRowMapper,BeanPropertyRowMapper从集合的角度讲他们俩的作用是一样的,都是将Account对象封装到集合中。 List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f); for(Account account : accounts){ System.out.println(account); } } /** * 定义Account的封装策略 */ class AccountRowMapper implements RowMapper<Account>{ /** * 把结果集中的数据封装到Account中,然后由spring把每个Account加到集合中 * @param rs * @param rowNum * @return * @throws SQLException */ @Override public Account mapRow(ResultSet rs, int rowNum) throws SQLException { Account account = new Account(); account.setId(rs.getInt("id")); account.setName(rs.getString("name")); account.setMoney(rs.getFloat("money")); return account; } } //查询一个 //使用RowMapper的方式:常用的方式 // List<Account> as = jt.query("select * from account where id = ? ", new AccountRowMapper(), 55); // 使用ResultSetExtractor的方式:不常用的方式 // Account account = jt.query("select * from account where id = ?", new AccountResultSetExtractor(),3); List<Account> accounts = jt.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),1); System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0)); //查询返回一行一列(使用聚合函数,但不加group by子句) Long count = jt.queryForObject("select count(*) from account where money > ?",Long.class,1000f); System.out.println(count);
当dao有很多个时,每个dao都有重复代码,能不能把他抽取出来呢?spring框架正好给我们提供了一个dbcDaoSupport,使用时我们可以让dao继承它。使用它的set方法,方便在xml中注入。
让Dao继承JdbcDaoSupport的方式,只能用于基于XML的方式,注解用不了。
public final void setDataSource(DataSource dataSource) {
if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
this.jdbcTemplate = this.createJdbcTemplate(dataSource);
this.initTemplateConfig();
}
}
第一:JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
第二:spring框架为我们提供了一组事务控制的接口。这组接口是在spring-tx-5.0.2.RELEASE.jar中。
1)PlatformTransactionManager 此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法
实现类:org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBC或iBatis 进行持久化数据时使用
–获取事务状态信息:TransactionStatue getTransaction(TransactionDefinition define)
–提交事务:void commit(TransactionStatus status)
–回滚事务:void rollback(TransactionStatus status)
2)TransactionDefinition 它是事务的定义信息对象
获取事务对象名称:String getName()
获取事务隔离级:int getIsolationLevel()
未提交读取(Read Uncommitted) Spring标识:ISOLATION_READ_UNCOMMITTED
已提交读取(Read Committed)Spring标识:ISOLATION_READ_COMMITTED。
可重复读取(Repeatable Read)Spring标识:ISOLATION_REPEATABLE_READ。
序列化(Serializable)Spring标识:ISOLATION_SERIALIZABLE。
读数据一致性 | 脏读 | 不可读 | 幻读 | |
---|---|---|---|---|
未提交读取 | 最低阶别,只能保证不读取物理上损坏的数据 | √ | √ | √ |
已提交读取 | 语句级 Oracle默认级 | × | √ | √ |
可重复读取 | 事务级 Mysql默认级别 | × | × | √ |
序列化 | 最高级别,事务级 | × | × | × |
获取事务传播行为:int getPropagationBehavior()
①REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选 择(默认值)
②SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
③MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
④REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
⑤NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
⑥NEVER:以非事务方式运行,如果当前存在事务,抛出异常
⑦NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。
获取事务超时时间:int getTimeOut() 默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
//读写型事务,怎删改,也会开启事务
获取事务是否只读:boolean isReadOnly() //只读型事务,执行查询时也会开启事务
3)TransactionStatus 此接口提供的是事务具体的运行状态
刷新事务:void flush()
获取是否存在存储点:boolean hasSavepoint()
获取事务是否完成:boolean isComplecated()
获取事务是否为新事物:boolean isNewTransaction()
获取事务是否回滚:boolean isRollBackOnly()
设置事务回滚:void setRollBackOnly()
第三:spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。
@Component("txManager") @Aspect public class TransactionManager { @Autowired private ConnectionUtils connectionUtils; @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){} /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } } @Around("pt1()") public Object aroundAdvice(ProceedingJoinPoint pjp){ Object rtValue = null; try { //1.获取参数 Object[] args = pjp.getArgs(); //2.开启事务 this.beginTransaction(); //3.执行方法 rtValue = pjp.proceed(args); //4.提交事务 this.commit(); //返回结果 return rtValue; }catch (Throwable e){ //5.回滚事务 this.rollback(); throw new RuntimeException(e); }finally { //6.释放资源 this.release(); } } } @Component("connectionUtils") public class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); @Autowired private DataSource dataSource; /** * 获取当前线程上的连接 * @return */ public Connection getThreadConnection() { try{ //1.先从ThreadLocal上获取 Connection conn = tl.get(); //2.判断当前线程上是否有连接 if (conn == null) { //3.从数据源中获取一个连接,并且存入ThreadLocal中 conn = dataSource.getConnection(); conn.setAutoCommit(false); tl.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /** * 把连接和线程解绑 */ public void removeConnection(){ tl.remove(); } }
导入xsi、aop和tx命名空间 <?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" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans> 在配置文件中配置业务层和持久层对 <!-- 配置service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 配置dao --> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- 注入dataSource --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"> </property> <property name="url" value="jdbc:mysql:///spring_day04"> </property> <property name="username" value="root"> </property> <property name="password" value="1234"></property> </bean> <!-- 配置一个事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入DataSource --> <property name="dataSource" ref="dataSource"></property> </bean> 配置事务的通知引用事务管理器 <!-- 事务的配置 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> </tx:advice> 配置事务的属性: <!--在tx:advice标签内部 配置事务的属性 --> <tx:attributes> <!-- 指定方法名称:是业务核心方法 read-only:是否是只读事务。默认false,不只读。 isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。 propagation:指定事务的传播行为。 timeout:指定超时时间。默认值为:-1。永不超时。 rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值, 任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。 --> <tx:method name="*" read-only="false" propagation="REQUIRED"/> <tx:method name="find*" read-only="true" propagation="SUPPORTS"/> </tx:attributes> 配置切入点表达式 <!-- 配置aop --> <aop:config> <!-- 配置切入点表达式 --> <aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/> </aop:config> <!-- 在aop:config标签内部:建立事务的通知和切入点表达式的关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
创建aop、xsi、context和tx命名空间
<!-- 配置spring创建容器时要扫描的包 --> <context:component-scan base-package="com.itheima"></context:component-scan> <!-- 配置JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置spring提供的内置数据源 --> <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_day02"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启spring对注解事务的支持 --> <tx:annotation-driven transaction-manager="transactionManager"/>
在业务层使用@Transactional注解
@Service("accountService") @Transactional(readOnly=true,propagation=Propagation.SUPPORTS) public class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao accountDao; @Override public Account findAccountById(Integer id) { return accountDao.findAccountById(id); } @Override @Transactional(readOnly=false,propagation=Propagation.REQUIRED) public void transfer(String sourceName, String targeName, Float money){ } } 该注解的属性和xml中的属性含义一致。该注解可以出现在接口上,类上和方法上。 出现接口上,表示该接口的所有实现类都有事务支持。 出现在类上,表示类中所有方法有事务支持 出现在方法上,表示方法有事务支持。 以上三个位置的优先级:方法>类>接口
核心容器的更新:Spring Framework 5.0 现在支持候选组件索引作为类路径扫描的替代方案。索引读取 &扫描类路径JetBrains Kotlin语言支持:响应式编程风格:Junit5支持:依赖类库的更新:终止支持一些类库
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。