full-stack轻量级开源框架
内核:
简单地将就是把对象的创建交给spring来管理,直接在XML文件中通过配置来完成对象创建需要的各类信息的注入,从而极大降低了类与类之间的依赖。
XML文件简单配置
一个bean标签对应一类对象,bean标签内可以配置对象各个属性,id是获取特定类的统一标识符,class是该类全类名。
IoC核心容器 CoreContainer
通过这种容器对象就可以轻松获取各种想要的bean对象。
容器对象主要由两种
ApplicationContext | BeanFactory |
---|---|
单例对象适用 | 多例对象适用 |
采用立即加载方式创建对象 | 采用延迟加载方式 |
所谓单例对象是这类对象即便要使用多次,但我们只打算用同一个对象;而多例对象将会创建很多次同一类的对象。像dao,service这种对象,我们主要是要调用它们的方法,而不同对象的方法都是相同的,因此这类对象一般都是单例的。但是单例对象线程不安全,这里需要注意。
所谓立即加载就是在创建容器对象、读取xml文件时就将对象加载进内存;而延迟加载就是创建容器对象时先不加载里面所bean,等到实际需要对象时再将其加载进内存。一静态,一动态。
一般来说,ApplicationContext更为常用。而它的获取方法主要由三种。
ClassPathXmlApplicationContext:它可以加载类路径下的配置文件 如,放置在resources目录下的"bean.xml"
FileSystemXmlApplicationContext:它可以加载磁盘仁义路径下的配置文件(“必须有访问权限”)如"C:\Users\jackson\Desklop\bean.xml"
AnnotationConfigApplicationContext:用于读取注解创建容器
代码如下
public class Client {
public static void main(String[] args) {
// ApplicationContext
// 1. 获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); // 立即加载
// 2. 根据id获取Bean对象
AccountService accountService = (AccountService) ac.getBean("accountService");
AccountDao accountDao = ac.getBean("accountDao", AccountDao.class);
System.out.println(accountService);
accountService.saveAccount();
System.out.println(accountDao);
// BeanFactory
Resource resource = new ClassPathResource("bean.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
AccountService accountService = (AccountService)beanFactory.getBean("AccountService"); // 延迟加载
System.out.println(accountService);
}
}
依赖注入的概念:
Dependency Injection
我的理解就是由于我们所创建的各个类之间存在的复杂的调用关系,比如网络编程中,Servlet需要创建Service对象并进行调用,Service对象需要创建Dao对象进行调用,那么在上述两个例子中,Service类的信息就注入到了Servlet中,而Dao类的信息就注入到了Service中。Servlet依赖于Service,而Service类依赖于Dao类。
IoC的作用: 降低程序间的耦合(依赖关系)
依赖关系的管理:
交给spring来维护。在当前类中需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明依赖关系即可。
依赖注入使用:
如前所述,依赖注入的配置主要通过编辑xml文件来完成,而xml文件中,我们主要关注的就是对应着实体类的bean标签,通过修改bean标签的各个属性以及标签体就可以完成这个bean标签所对应的类的注入配置。
属性名 | 作用 |
---|---|
type | 用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些要注入的数据类型 |
name | 用于指定给对象中指定名称的参数赋值 |
value | 用于提供基本类型和String类型的数据 |
ref | 用于指定其他的bean类型数据,一般一个ref = 另一个bean标签的id |
scope | 单例对象/多例对象或者对象作用域 |
能注入的数据类型有三类:
- 基本类型和String 直接用value属性配置即可
- 其他bean类型(在配置文件或者注解配置过的bean) 一般需要ref
- 复杂类型/复合类型 比如list、Map这样的迭代器类型,还需要在property配置其标签体
注入的方式有三种:
- 使用构造函数提供 xml配置 constructor-arg 这一子标签 , 要求类中要有相应的构造方法
- 使用set方法提供 xml配置 property 这一子标签, 要求类中要有相应的setter
- 使用注解提供
例如,配置一bean对象
// 配置姓名属性
// 配置一个字符串数列
AAA
BBB
CCC
// 配置一个map
ddd
eee
<?xml version="1.0" encoding="UTF-8"?>
<!--suppress SpringFacetInspection -->
<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">
<!--构造函数注入:
使用的标签:constructor-arg
标签位置:bean标签内部
标签属性
type: 用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些要注入的数据类型
index: 用于指定要注入的数据给构造函数中指定索引位置的参数赋值
name: 用于指定给构造函数中指定名称的参数赋值
===============以上三个用于指定给构造函数中哪个参数赋值==================================
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。即在spring的IoC核心容器中出现过的bean对象
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须注入到对象中
-->
<bean id="accountService" class="com.zjuee.service.impl.AccountServiceImpl" scope="prototype">
<constructor-arg name="name" value="test"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="birthday" ref="now"/>
<constructor-arg name="accountDao" ref="accountDao"/>
</bean>
<!--配置一个日期对象,并将其读取到了ApplicationContext容器中-->
<bean id="now" class="java.util.Date" scope="prototype"/>
<!--配置AccountDao的bean对象,并将其注入到accountService2与accountService1中-->
<bean id="accountDao" class="com.zjuee.dao.impl.AccountDaoImpl"/>
<!--set方法注入 更常用
涉及的标签:property
出现的位置:bean标签的内部
标签属性
type: 用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些要注入的数据类型
name: 用于指定给对象中指定名称的参数赋值
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。即在spring的IoC核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端
存在某个成员必须有值,但获取对象时的set方法可能没有执行
-->
<bean id="accountService2" class="com.zjuee.service.impl.AccountServiceImpl2" scope="prototype">
<property name="name" value="test"/>
<property name="age" value="22"/>
<property name="birthday" ref="now"/>
<property name="accountDao" ref="accountDao"/>
</bean>
<!--复杂类型的注入/集合类型的注入
用于给List结构集合注入的标签:
list array set
用于给Map结构集合注入的标签:
map props
结构相同,标签可以互换
-->
<bean id="accountService3" class="com.zjuee.service.impl.AccountServiceImpl3" scope="prototype">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myMap">
<props>
<prop key="testD">ddd</prop>
<prop key="testE">eee</prop>
</props>
</property>
<property name="myProp">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB" value="bbb"></entry>
<entry key="testC" value="ccc"></entry>
</map>
</property>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置Service对象-->
<bean id="accountService" class="com.zjuee.service.impl.AccountServiceImpl">
<!--注入dao对象-->
<property name="accountDao" ref="accountDao"/>
</bean>
<!--配置dao对象-->
<bean id="accountDao" class="com.zjuee.dao.impl.AccountDaoImpl">
<!--注入QueryRunner-->
<property name="runner" ref="runner"/>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"/>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
除了使用xml文件配置各类对象信息外,我们还可以使用基于注解的方法,个人感觉注解的方法优势在于简单,但是使用注解有很多细节需要注意,相比于xml犯错的概率也更高。因此使用注解进行配置时要格外小心。
作用于xml文件中的 bean标签一样,在spring容器中加载对象并指明对象的id。
@Component
@Controller 表现层
@Service 服务层
@Repository 持久层
以上三个注解他们的作用和属性与Component是一样的.
他们三个是spring框架为我们提供的明确的三层使用的注释,使我们三层对象更加清晰
作用就和在xml配置文件中的bean标签中写一个 property 标签的作用是一样的
@AutoWried: (自动连线?)
@Service("accountService") // 在容器中创建Bean对象,id为accountService
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao; // 自动连接容器中AccountDao类的实现类对象
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
}
@Qualifier
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("dataSource2") DataSource dataSource) { // 我们需要id为dataSource2的连接池bean对象
return new QueryRunner(dataSource);
}
@Resource
@Value
他们的作用就和在bean标签使用scope属性实现的功能一样
@Scope
它们的作用就和在bean标签中使用init-method和destroy-method的作用是一样的
@PreDestroy
用于指定销毁方法
@PostConstruct
用于指定初始化方法
使用注解也可以分为两种情况,仍然依赖xml配置中约束的信息或者完全摆脱xml文件,依赖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">
<context:component-scan base-package="com.zjuee"/>
</beans>
完全独立的注解配置需要有一个描述配置信息的类文件,并在主函数获取核心容器ApplicationContext时传递配置信息类文件的字节码对象。
获取格式如下:
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class)
那么如何编写配置类的代码呢?示例如下:
/**
* 主配置类
*/
@Configuration
@ComponentScan({"com.zjuee"}) // 指向两个待扫描的包
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration{}
/**
* JDBC配置类
*/
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("dataSource2") DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(name = "dataSource1")
public ComboPooledDataSource createDataSource() {
ComboPooledDataSource ds = new ComboPooledDataSource();
try {
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
} catch (Exception e) {
throw new RuntimeException(e);
}
return ds;
}
@Bean(name = "dataSource2")
public ComboPooledDataSource createDataSource2() {
ComboPooledDataSource ds = new ComboPooledDataSource();
try {
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring2");
ds.setUser(username);
ds.setPassword(password);
} catch (Exception e) {
throw new RuntimeException(e);
}
return ds;
}
}
上面的示例中,SpringConfiguration是主配置文件,也称为父配置文件,我们可以在这一主配置文件中完成其他子配置文件的布局。
而JdbcConfiguration就是其中之一子配置文件,里面完成数据库相关类对象的配置。
可以看到,注解配置与xml配置在本质上是相同的。
各个注解解释如下:
@Configuration
@ComponentScan
作用:用于通过注解指定spring在创建容器时要扫描的包
属性:
value:和basePackages的作用是一样的,都是创建容器时指定要扫描的包
等同于在xml中配置
@Bean
作用:用于把当前方法的返回值作为bean对象存入到spring的ioc容器中
属性:
name:用于指定bean的id。不写时默认是当前方法的名称
等同于在xml中配置
细节:
当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,
查找方式和Autowired是一样的。
@Scope
@Value
@Import
@PropertySource
@PreDestroy
@PostConstruct
通过配置的(xml或anno)方式,实现动态代理,对业务层/控制层/持久层各对象进行增强,以满足不同的需求。
比如说:可以通过AOP,将业务层中的各CRUD操作封装进一个事务中,提高数据库的安全性。
JoinPoint 连接点 被代理类的所有成员方法
Pointcut 切入点 被拦截增强的方法
Advice 通知 拦截到切入点之后要做的事
知类型 | 执行位置 |
---|---|
前置通知 | invoke前 初始化事务时 |
后置通知 | invoke后 事务提交后 |
异常通知 | catch 出现异常后 |
最终通知 | finally 事务结束,释放资源时 |
环绕通知 | 全体部分,自己通过注解、代码等方式指定 |
Introduce 引介
Target 代理的目标对象
Weaving 织入 把增强应用到目标对象中得到代理对象
Proxy 代理
Aspect 切入点和通知的集合
配置事务管理器
配置事务的通知
此时需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用 tx:advice 标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager 给事务通知提供一个事务管理器引用
配置事务的属性
在事务的通知 tx:advice 标签的内部,使用 tx:attributes 标签配置事务的属性。
method: 配置的目标方法名
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别
propagation:用于指定事物的传播行为。默认值是REQUIRED,表示一定会有失误,增删改的选择。查询方法可以选择SUPPORTS
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写
timeout: 用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for: 用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事物不会滚。没有默认值,表示任何异常都回滚
no-rollbackf-for: 用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事物回滚。没有默认值,表示任何异常都回滚
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false" />
<tx:method name="find" propagation="REQUIRED" read-only="true" />
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.zjuee.service.impl.*.*(..))"/>
<!--建立切入点表达式和事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
配置事务管理器
开启spring对注解事务的支持
<!--声明采用注解方式,指定spring要扫描的包-->
<context:component-scan base-package="com.zjuee"/>
<!--xml配置文件中开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly=true)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void transfer(String sourceName, String targetName, Float money) {
// 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);
// 2.6. 更新转入账户
accountDao.updateAccount(target);
}
public Account findAccountById(Integer id) {
return accountDao.findAccountById(id);
}
}
注意:使用注解时xml配置文件的约束空间要新命名context,对应的aop命名空间不再需要
<?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: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">
全注解即不再需要xml配置文件,主要通过@Configuration配置类完成spring信息配置。
账户的持久层实现类
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate template;
public Account findAccountById(Integer id) {
List<Account> accounts = template.query("select * from account where id = ? ", new BeanPropertyRowMapper<Account>(Account.class), id);
return accounts.isEmpty() ? null : accounts.get(0);
}
public Account findAccountByName(String name) {
List<Account> accounts = template.query("select * from account where name = ? ", new BeanPropertyRowMapper<Account>(Account.class), name);
if(accounts.isEmpty()) {
return null;
}
if(accounts.size() > 1) {
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
template.update("update account set name = ? , money = ? where id = ? ",
account.getName(), account.getMoney(), account.getId());
}
public List<Account> findAllAccount() {
return template.query("select * from account ", new BeanPropertyRowMapper<Account>(Account.class));
}
}
业务层实现类
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly=true)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void transfer(String sourceName, String targetName, Float money) {
// 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);
// 2.6. 更新转入账户
accountDao.updateAccount(target);
}
public Account findAccountById(Integer id) {
return accountDao.findAccountById(id);
}
}
spring的配置类,相当于bean.xml
@Configuration
@ComponentScan("com.zjuee")
@Import({JdbcConfig.class, TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement // 开启事务的注解配置声明
public class SpringConfiguration {
}
和事务相关的配置类
public class TransactionConfig {
/**
* 配置事务管理器
* @param dataSource
* @return
*/
@Bean("transactionManager")
public PlatformTransactionManager createTxManager(DataSource dataSource) {
return new DataSourceTransactionManager(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对象
* @param dataSource
* @return
*/
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name = "dataSource")
public DataSource createDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class TestSpringTxManager {
@Resource(name = "accountService")
private AccountService as;
@Test
public void testFind() {
Account account = as.findAccountById(3);
System.out.println(account);
}
@Test
public void testTransfer() {
as.transfer("bbb","aaa",200f);
}
}
主要参考:传智播客视频教程
Spring官方文档:spring-framework-5.0.2.RELEASE-dist/spring-framework-5.0.2.RELEASE/docs/spring-framework-reference/index.html