Spring
概述
Spring概念
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring
MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
Spring优势
- 方便解耦,简化开发
- AOP 编程的支持
- 声明式事务的支持
- 方便程序的测试
- 方便集成各种优秀框架
- 降低 JavaEE API 的使用难度
- Java 源码是经典学习范例
解耦
什么是耦合?
程序间的依赖关系,包括 类之间的依赖 方法间的依赖.通过解耦降低程序间的依赖关系.实际开发中,应该做到,编译器不依赖,运行时才依赖
下面看两个耦合程序解耦的案例
案例一:注册驱动依赖于com.mysql.jdbc.Driver导致独立性差
package com.xupt.jdbc;
import java.sql.*;
public class JdbcDemo01 {
public static void main(String[] args) throws Exception {
//1.注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2,获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "921100");
//3.获取操作数据库的预处理对象
String sql="select * from user";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//4.执行SQL,得到结果集
ResultSet resultSet = preparedStatement.executeQuery();
//5.遍历结果集
while (resultSet.next()){
System.out.println(resultSet.getString("name"));
}
//6.释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
解耦思路
第一步 使用反射来创建对象,而避免使用new关键字
第二歩,通过读取配置文件来获取要创建的对象的全限定类名
案例二:
持久层
public interface IUserDao {
void saveUser();
}
public class UserDaoImpl implements IUserDao {
public void saveUser() {
}
}
业务层
public interface IUserService {
void saveUser( );
}
public class UserServiceImpl implements IUserService {
//耦合
private IUserDao user=new UserDaoImpl();
public void saveUser() {
System.out.println("保存了用户");
}
}
视图层
//模拟一个表现层,用于调用业务层(实际开发中为Servlet)
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.saveUser();
}
}
解决方案
package com.itheima.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 > 实体类
*
* 它就是创建我们的service和dao对象的。
*
* 第一个:需要一个配置文件来配置我们的service和dao
* 配置的内容:唯一标识=全限定类名(key=value)
* 第二个:通过读取配置文件中配置的内容,反射创建对象
*
* 我的配置文件可以是xml也可以是properties
*/
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String,Object>();
//取出配置文件中所有的Key
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个Key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据bean的名称获取对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
/**
* 根据Bean的名称获取bean对象
* @param beanName
* @return
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
// System.out.println(beanPath);
bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
}
Spring架构
IOC概念
快速入门
基于XML的配置
让Spring管理资源,在配置文件中配置service和dao
<?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">
<bean id="UserService" class="com.xupt.service.impl.UserServiceImpl"></bean>
<bean id="UserDao" class="com.xupt.dao.impl.UserDaoImpl"></bean>
</beans>
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
IUserDao userDao = (IUserDao) ac.getBean("UserDao");
IUserService userService = ac.getBean("UserService", IUserService.class);
ApplicationContext的三个常用实现类:
ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
AnnotationConfigApplicationContext:它是用于读取注解创建容器的。
核心容器的两个接口引发出的问题
ApplicationContext 单例模式适用
它在构建核心容器时,创建对象采取的是立即加载的方式.也就是说,只要一读取完配置文件马上就创建配置文件中的对象
BeanFactory 多例模式适用
它在构建核心容器时,创建对象采取的是采用延迟加载的方式.也就是说,什么时候根据id获取对象了,什么时候才是真正的创建对象
Spring中bean标签细节
创建bean的三种方式
方式1: 默认构造函数创建
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
方式2: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
方式3:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
bean对象的作用范围
bean的作用范围调整
bean标签的scope属性:
作用:用于指定bean的作用范围
取值: 常用的就是单例的和多例的
- singleton:单例的(默认值)
- prototype:多例的
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>
bean对象的生命周期
bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
Spring的依赖注入
依赖注入:Dependency Injection
IOC的作用: 降低程序间的耦合(依赖关系)
依赖关系的管理:以后都交给spring来维护. 在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
依赖关系的维护:就称之为依赖注入。
依赖注入
能注入的数据:有三类
- 基本类型和String
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
注入的方式:有三种
- 第一种:使用构造函数提供
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性
type
:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index
:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
name
:用于指定给构造函数中指定名称的参数赋值 常用的
value
:用于提供基本类型和String类型的数据
ref
:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="Mark"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
- 第二种:使用set方法提供
涉及的标签:property
出现的位置:bean
标签的内部
标签的属性
name
:用于指定注入时所调用的set方法名称
value
:用于提供基本类型和String类型的数据
ref
:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
<property name="name" value="TEST" ></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
- 第三种:使用注解提供
基于注解的IOC配置
曾经XML的配置:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="" init-method="" destroy-method="">
<property name="" value="" | ref=""></property>
</bean>
快速入门
切记拷贝aop的jar包
使用@Component
注解配置资源管理
@Service("accountService")
创建Spring的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"
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在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为
context名称空间和约束中-->
<context:component-scan base-package="com.itheima"></context:component-scan>
</beans>
常用注解
用于创建对象的
他们的作用就和在XML配置文件中编写一个标签实现的功能是一样的
@Component
:
作用:用于把当前类对象存入spring容器中
属性:
value
:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
@Controller
:一般用在表现层
@Service
:一般用在业务层
@Repository
:一般用在持久层
以上三个注解他们的作用和属性与Component是一模一样。
他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
用于注入数据的
他们的作用就和在xml配置文件中的bean标签中写一个< property>标签的作用是一样的
@Autowired
:
作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
如果Ioc容器中有多个类型匹配时:
出现位置:
可以是变量上,也可以是方法上
细节:
在使用注解注入时,set方法就不是必须的了。@Qualifier:
作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以.
属性:
value:用于指定注入bean的id。
@Qualifier源码
@Resource
作用:直接按照bean的id注入。它可以独立使用
属性:
name:用于指定bean的id。
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
另外,集合类型的注入只能通过XML来实现。
Value
作用:用于注入基本类型和String类型的数据
属性:
value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
SpEL的写法:${表达式}
用于改变作用范围的
他们的作用就和在bean标签中使用scope属性实现的功能是一样的
Scope
作用:用于指定bean的作用范围
属性:
value
:指定范围的取值。常用取值:singleton prototype
和生命周期相关的
他们的作用就和在bean标签中使用init-method
和destroy-methode
的作用是一样的
@PreDestroy
作用:用于指定销毁方法@PostConstruct
作用:用于指定初始化方法
其他注解
@Configuration
作用:指定一个配置类,它的作用和bean.xml是一样的
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写.@ComponentScan()
作用:用于通过注解指定spring在创建容器时要扫描的包
属性:value
它和basepackages的作用是一样的,都用于指定创建容器时要扫描的包.我们使用此注解就等同于在xml中配置了
componentStan源码
@Bean
作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:name
用于指定bean的id,当不写时,默认值是当前方法的名称
细节:
当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,查找的方法和@Autowired
注释的作用一样.
/**
* 和spring连接数据库相关的配置类
*/
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对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="ds1")
public DataSource createDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url1);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
此时使用AnnotationConfigApplicationContext(SpringConfiguration.class)
获取
@Import
作用:用于导入其他的配置类
属性:value 用于指定其他配置类的字节码
当我们使用import的注解后,有import注解的类就是父配置类,而导入的都是子类
@PropertySource
作用:用于加载 .properties文件中的配置.例如我们配置数据源时,可以把连接数据库的信息写到properties配置文件中,就可以使用此注解指定properties配置文件的位置
Spring整合Junit
在应用程序中,程序的入口为main方法;
Junit单元测试中,没有main方法也能执行.Junit集成了一个main方法,该方法就会判断当前测试类中哪些方法有@Test注解,Junit就让有Test注解的方法执行.
在执行测试方法时,Junit根本不知道我们是不是使用了Spring框架,所以也就不会为我们读取配置文件/配置类创建Spring核心容器.
因此,当测试方法执行时,没有IOC容器,就算写了AutoWrited注解,也无法实现注入.
配置步骤
步骤1 导入spring整合Junit的jar包
步骤2 使用Junit提供的一个注解@RunWith
把原有的main方法替换了,替换成Spring提供的
步骤3 告知Spring的运行器,spring和IOC创建是基于xml还是注解的,并说明
位置
@ContextConfiguration
locations: 指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在地位置
注意:当我们使用Spring 5.x版本时,要求Junit的jar必须是4.12及以上
Spring中AOP
案例分析
账户实体类
/**
* 账户的实体类
*/
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 +
'}';
}
}
业务层接口
/**
* 转账
* @param sourceName 转出账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
void transfer(String sourceName,String targetName,Float money);
业务层实现类
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
// int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
注意:需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个能控制事务的对象
持久层实现类
@Override
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
ConnectionUtils
连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
//注入
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
TransactionManager
和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
public class TransactionManager {
//获取connection连接
private ConnectionUtils connectionUtils;
//实现依赖注入
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
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();
}
}
}
配置
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
动态代理
生产者
public class Producer implements IProducer{
/**
* 销售
* @param money
*/
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
接口
/**
* 对生产厂家要求的接口
*/
public interface IProducer {
/**
* 销售
* @param money
*/
public void saleProduct(float money);
/**
* 售后
* @param money
*/
public void afterService(float money);
}
消费者
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
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);
}
}
动态代理:
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:
基于接口的动态代理
基于子类的动态代理
基于接口的动态代理:
涉及的类:Proxy
提供者:JDK官方
如何创建代理对象:
使用Proxy类中的newProxyInstance
方法
创建代理对象的要求:
被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance
方法的参数:
ClassLoader
:类加载器
它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。 固定写法。
producer.getClass().getClassLoader()
Class[]
:字节码数组
它是用于让代理对象和被代理对象有相同方法。固定写法。
producer.getClass().getInterfaces()
InvocationHandler
:用于提供增强的代码
它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
new InvocationHandler() {
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;
}
}
invoke()
作用:执行被代理对象的任何接口方法都会经过该方法
方法参数的含义:
proxy
代理对象的引用
method
当前执行的方法
args
当前执行方法所需的参数
返回值:和被代理对象方法有相同的返回值
Enhancer
Enhancer由第三方cglib库提供,要求被代理类不能是最终类,是基于子类的动态代理
生产者
public class Producer {
/**
* 销售
* @param money
*/
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
create
方法的参数:
Class
:字节码
它是用于指定被代理对象的字节码。
Callback
:用于提供增强的代码
它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
我们一般写的都是该接口的子接口实现类:MethodInterceptor
proxy
,method
,args
三个参数和基于接口的动态代理中invoke方法的参数是一样的
methodProxy
当前执行方法的代理对象
消费者
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
@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);
}
}
通过动态代理对上述代码进行改造
BeanFactory
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 获取Service代理对象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.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 {
if("test".equals(method.getName())){
return method.invoke(accountService,args);
}
Object rtValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return rtValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
}
});
}
}
AOP概念
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个 重要内容,是函数式编程的一种衍生泛型
作用及优势
- 减少重复代码
- 提高开发效率
- 维护方便
相关术语
Joinpoint 连接点
所谓连接点是指那些被拦截的点,在Spring中,这些点指的是方法,因为Spring支持方法类型的连接点
Pointout 切入点
所谓切入点是指我们要对哪些Jointpoint进行拦截的意思
被增强的连接点
Advice(通知/增强)
所谓通知是指拦截到Jointpoint之后要做的事情就是通知
通知的类型:
前置通知,后置通知,异常通知,最终通知,环绕通知
整个的invoke方法在执行就是环绕通知,在环绕通知中有明确的切入点方法调用
Introduction 引介
引介是一种特殊的通知在不修改代码的前提下,Introduction可以在运行期为类动态地添加一些方法或成员
Target 目标对象
代理的目标对象
Weaving织入
是把增强应用到目标对象来创建新的代理对象的过程
Proxy代理
一个类被AOP织如增强后,就产生一个结果代理类
Aspect切面
是切入点和通知(引介)的结合
基于XML的AOP配置
步骤1:导入aop约束
<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"
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">
</beans>
步骤2:配置spring的IOC
<!-- 配置srping的Ioc,把service对象配置进来-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
步骤3:配置AOP
在aop:aspect
标签的内部使用对应标签来配置通知的类型
我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
<!--配置AOP-->
<aop:config>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
步骤4:测试
/**
* 测试AOP的配置
*/
public class AOPTest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
as.updateAccount(1);
as.deleteAccount();
}
}
切入点表达式
- 通配符写法:
* *..*.*()
- 标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
- 访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
- 返回值可以使用通配符,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
- 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个
*.
* *.*.*.*.AccountServiceImpl.saveAccount()
- 包名可以使用…表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
- 类名和方法名都可以使用*来实现通配
* *..*.*()
- 参数列表
可以直接写数据类型:
基本类型直接写名称int
引用类型写包名.类名的方式java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用…表示有无参数均可,有参数可以是任意类型
注意: 实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.itheima.service.impl..(…)
通知类型
注意:异常通知和后置通知只能执行一个
环绕通知
问题: 当我们配置了环绕通知后,切入点方法没有执行,而通知方法执行了
分析: 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有
解决:
Spring框架为我们提供一个接口:ProceedingJoinPoint,该接口有一个方法proceed(),此方法就相当于明确调用切入点方法.
该接口可以作为环绕通知的方法 参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用.
基于注解的AOP配置
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
Spring中JdbcTemplate
它是spring框架中提供的一个对象,是对原始Jdbc API对象的简单封装,spring框架为我们提供了许多的操作模板类
操作关系型数据的:
JdbcTemplate
HibernateTemplate
操作nosql数据库的:
RedisTemplate
操作消息队列的:
JmsTemplate
案列1
实体类
/**
* 账户的实体类
*/
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 +
'}';
}
}
JdbcTemplate
/**
* JdbcTemplate的最基本用法
*/
public class JdbcTemplateDemo1 {
public static void main(String[] args) {
//准备数据源:spring的内置数据源
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/eesy");
ds.setUsername("root");
ds.setPassword("1234");
//1.创建JdbcTemplate对象
JdbcTemplate jt = new JdbcTemplate();
//给jt设置数据源
jt.setDataSource(ds);
//2.执行操作
jt.execute("insert into account(name,money)values('ccc',1000)");
}
}
持久层
/**
* 账户的持久层接口
*/
public interface IAccountDao {
/**
* 根据Id查询账户
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
持久层实现类
/**
* 账户的持久层实现类
*/
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
案列2
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!--<property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置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>
</beans>
JdbcTemplate
/**
* JdbcTemplate的最基本用法
*/
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)");
}
CRUD
//保存
jt.update("insert into account(name,money)values(?,?)","eee",3333f);
//更新
jt.update("update account set name=?,money=? where id=?","test",4567,7);
//删除
jt.update("delete from account where id=?",8);
//查询所有
List<Account> accounts = jt.query("select * from account where money > ?",new AccountRowMapper(),1000f);
List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
for(Account account : accounts){
System.out.println(account);
}
//查询一个
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);
/**
* 定义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;
}
}
Dao
IAccountDao
/**
* 账户的持久层接口
*/
public interface IAccountDao {
/**
* 查询所有
* @return
*/
List<Account> findAllAccount();
/**
* 查询一个
* @return
*/
Account findAccountById(Integer accountId);
/**
* 保存
* @param account
*/
void saveAccount(Account account);
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 删除
* @param acccountId
*/
void deleteAccount(Integer acccountId);
/**
* 根据名称查询账户
* @param accountName
* @return 如果有唯一的一个结果就返回,如果没有结果就返回null
* 如果结果集超过一个就抛异常
*/
Account findAccountByName(String accountName);
}
AccountDaoImpl
/**
* 账户的持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtils connectionUtils;
@Override
public List<Account> findAllAccount() {
try{
return runner.query(connectionUtils.getThreadConnection(),"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(connectionUtils.getThreadConnection(),"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(connectionUtils.getThreadConnection(),"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(connectionUtils.getThreadConnection(),"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(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}