bean装配
- Spring可以管理bean,也即是免new直接实例化一个对象,也可以将对象注入到其他类中。默认单例模式管理。
- 步骤:定义一个实体类,bean.xml注册bean,在测试类获取该bean并使用它的成员方法。
bean.xml注册bean:
<!-- 注册bean --> <bean id="permissionDao" class="com.haien.shirochapter12.dao.impl.PermissionDaoImpl"/> <!-- Service:注入到其他bean中 --> <bean id="permissionService" class="com.haien.shirochapter12.service.impl.PermissionServiceImpl"> <property name="permissionDao" ref="permissionDao"/> </bean>
- 其中,属性id和name一样,都是指定bean的名称,只是id要求唯一(推荐),而name则可以重复,当获取bean时,会返回名称相同的第一个;当id和name都缺失时,bean名为class类全名。
- 则PermissionServiceImpl必须有PermissionDaoImpl的setter,否则报错
public class PermissionServiceImpl implements PermissionService {
public void setPermissionDao(PermissionDao permissionDao){
this.permissionDao=permissionDao;
}
}
- 代码实例:ideaProjects/shirochapter12/resources/spring-beans.xml、service/impl/PermissionServiceImpl
- 代码实例:Spring1ManageBean
IOC入门
- 控制权反转:解耦,改内部类为参数传入形式,用set(接口)的方式来使方法调用者能动态指定。
- 代码例子:有一个任务JavaWork,它的主要工作就是测试,所以它的主要方法就是doTest()。我们可以在这个方法中实例化一个测试员Tester的对象,并调用它的test()方法进行测试,但是这样我们要换人就留必须修改实例化的代码,所以我们决定让doTest()把控制权转交到JavaWork的调用者手上。我们在JavaWork中写一个setTester的方法,让JavaWork的调用者通过这个方法来指定测试员。那么这些测试员就都要实现一个相同的含有test()的接口,所以setTester()的入参类型就是这个接口了。
- 代码实例:Spring2IOCFirst
IOC详解
- 依赖注入:也就是怎么把属性为其他实体类的类注册成bean
- 代码实例:Spring3BeanZhuRu(普通属性)、Spring4BeanZhuRu2(含类属性)、Spring5BeanAutoWire(指定by某种方式注入)
- 方法注入:配合scope=prototype实现动态注入
- 代码实例:Spring5FunctionAutoWire
bean之间的关系
- 继承、依赖、引用
- 代码实例:Spring6RelationOfBeans
bean作用范围
- 重点是singleton(默认)和prototype。singleton是指整个IOC容器中就只有一个bean,就算再申请一个也只是把原来的那个返回,SpringBoot中用注解注入的bean也是这样;prototype则每申请一次就返回一个新的bean
- 切换bean作用范围参见笔记:Shiro第十六章-综合实例
- 代码实例:Spring5FunctionAutoWire
FactoryBean
- 在spring中可以利用FactoryBean来产生一些自定义配置的bean。
AOP
简介
- 面向切面编程,是软件开发中的一个热点。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低。
- 主要功能:日志记录、性能设计、安全控制、事务处理、异常处理等等。
举个例子,旧式的日志管理是这样的:
public class StudentServiceImpl implements StudentService{ @Override public void addStudent(String name) { // 日志记录 System.out.println("开始添加学生"+name); System.out.println("添加学生"+name); // 日志记录 System.out.println("完成学生"+name+"的添加"); /*这种日志记录方式入侵了逻辑代码,增加了代码的耦合度,完全不一样作用的代码也写到了一起*/ } }
- 代码实例:Spring7AOPFirst
详解
各种通知对逻辑代码的切入
- 前置通知:方法执行前切入
- 后置通知
- 环绕通知(前置+后置)
- 异常通知
代码实现
编写逻辑代码
public class StudentServiceImpl implements StudentService{ @Override public void addStudent(String name) { System.out.println("添加学生"+name); } }
定义通知切面类
/* * 通知切面类 */ public class StudentServiceAspect { //前置通知 public void doBefore(JoinPoint jp) { System.out.println("类名:"+jp.getTarget().getClass().getName()); //jp可以获取类名,这里应该是StudentServiceImpl System.out.println("方法名:"+jp.getSignature().getName()); //addStudent System.out.println("开始添加学生:"+jp.getArgs()[0]); //获取参数,返回数组,这里只有一个参数,所以直接拿出第0个 } //后置通知 public void doAfter(JoinPoint jp) { System.out.println("类名:"+jp.getTarget().getClass().getName()); //jp可以获取类名,这里应该是StudentServiceImpl System.out.println("方法名:"+jp.getSignature().getName()); //addStudent System.out.println("学生添加完成:"+jp.getArgs()[0]); //获取参数,返回数组,这里只有一个参数,所以直接拿出第0个 } }
3.配置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" 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"> <!-- 使用切面应该多加上面三行带“aop”的东西 -->
装配bean(逻辑代码+切面)
<bean id="studentServiceAspect" class="com.java1234.advice.StudentServiceAspect"></bean> <bean id="studentService" class="com.java1234.service.impl.StudentServiceImpl"></bean>
配置切面
<aop:config> <!-- 定义切面 --> <aop:aspect id="studentServiceAspect" ref="studentServiceAspect"> <!-- 定义切点 --> <!-- execution表示执行,括号内写方法,*表示任意(第一个表示任意返回值, 第二个表示以前面为前缀的任意包,第三个表示包下任意方法;这样就能匹配 service接口和实现中的任意方法了),..表示方法接受任意(数目、类型)参数 --> <!-- 定义通知 --> <aop:pointcut expression="execution(* com.java1234.service.*.*(..))" id="businessService"/> <aop:before method="doBefore" pointcut-ref="businessService"/> <!-- 执行切点businessService之前执行doBefore方法 (执行“添加学生”之前先打印“开始添加学生”)--> <aop:after method="doAfter" pointcut-ref="businessService"/> </aop:aspect> </aop:config>
- 匹配规则:
- ()匹配了一个不接受任何参数的方法,(..)匹配一个接受任何数量、类型参数的方法,()匹配了一个接受任何类型参数的方法,(,String)则第一个参数可以是任何类型,但第二个必须是String。
- 任意公共方法:execution(public (..))
- 任意以“set”开头的方法:execution( set(..))
- AccountService接口中的任意方法:execution( com.xyz.service.AccountService.(..))
- service包的任意方法:execution( com.xyz.service..*(..))
- service包及其子包的任意方法:execution( com.xyz.service..\.*(..)) //..匹配任何数量子包,包括零个
- +:匹配指定类型的子类型:execution( com.haien.chapter16..service..\+.*(..))
- java.*.String: 匹配java包下任意一级子包下的String类,如匹配java.lang.String,但不匹配java.lang.ss.String。
- java..* :匹配java包及任何子包下的任何类型;如匹配java.lang.String、java.lang.annotation.Annotation
- java.lang.Number+:匹配java.lang包下的任何Number的子类型;如匹配java.lang.Integer,也匹配java.math.BigInteger
- 参考文章
- 更详细的通配符说明
- 测试执行
public class T {
private ApplicationContext ac;
public static void main(String[] args) {
ApplicationContext aContext=new ClassPathXmlApplicationContext("beans.xml");
StudentService studentService=(StudentService)aContext.getBean("studentService");
studentService.addStudent("张三");
}
}
- 测试结果:开始添加学生:张三
- 添加学生张三
- 学生添加完成:张三
- 其他通知参见代码实例:Spring8AOPDetails
jdbc支持
- Spring使用JdbcTemplate封装了jdbc的配置
- 代码实例:Spring9JdbcTemplate(实现对学生的增删查改)
- JdbcDaoSupport的使用:能使JdbcTemplate的使用简单一点点
- 代码实例:Spring9JdbcDaoSupport
- NamedParameterJdbcTemplate的使用:可以替代JdbcTemplate,支持命名的参数变量,具体体现在用问号蹲坑的形式被替换为用命名参数蹲坑,代码可读性up。
- 代码实例:Spring10NamedParameterJdbcTemplate(主要是把Spring9JdbcTemplate的beans.xml和StudentDaoServiceImpl.java的JdbcTemplate类替换为NamedParameterJdbcTemplate类)
Spring对事务的支持
编程式事务管理:业务代码受到入侵
public void transferAccounts(final int count, final int userIdA, final int userIdB) { //因为内部类问题要加final
//编程式事务管理,缺点:乱七八糟的代码侵入到业务逻辑里面了,这里的业务逻辑就两个方法而已
transactionTemplate.execute(new TransactionCallbackWithoutResult() { //new一个内部类
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
//把方法加进来这两个方法就在一个事务中了
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
});
}
- 代码实例:Spring11BianchengTransaction(转账的例子)
使用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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 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/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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 使用Spring的事务管理应该多加上面三行带“tx”的东西,aop的也不能少 -->
然后配置事务管理
<!-- jdbc事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 暂定事务对所有方法都切入 --> <tx:method name="*" /> <!-- 不过常见的配置应该像下面这样 --> <!-- 以下配置是有优先级的,假如前面方法名都扫描不到, 那么应用最后的*,也即是扫描任意方法 --> <tx:method name="insert*" propagation="REQUIRED" /> <!-- propagation传播方式为required,当没有事务时自动创建事务 (最常见的传播方式配置) --> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="edit*" propagation="REQUIRED" /> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="new*" propagation="REQUIRED" /> <tx:method name="set*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="change*" propagation="REQUIRED" /> <tx:method name="get*" propagation="REQUIRED" read-only="true" /> <!-- 只是查询数据库的方法则设为只读 --> <tx:method name="find*" propagation="REQUIRED" read-only="true" /> <tx:method name="load*" propagation="REQUIRED" read-only="true" /> <tx:method name="*" propagation="REQUIRED" read-only="true" /> </tx:attributes> </tx:advice> <!-- 配置事务切面 --> <aop:config> <!-- 配置切点 --> <aop:pointcut id="serviceMethod" expression="execution(* com.java1234.service.*.*(..))" /> <!-- 配置事务通知 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/> </aop:config>
- 逻辑代码清清爽爽,事务自动切入
public void transferAccounts(int count, int userIdA, int userIdB) {
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
- 代码实例:Spring12StatementTransaction
使用注解配置声明式事务管理
引入tx的命名空间,xml配置如下
<!-- jdbc事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> <!--不用配置事务切面了-->
- 在service实现层添加@Transactional注解
@Transactional
public class BankServiceImpl implements BankService{
private BankDao bankDao;
public void setBankDao(BankDao bankDao) {
this.bankDao=bankDao;
}
@Override
public void transferAccounts(int count, int userIdA, int userIdB) {
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
}
- 代码实例:Spring12AnnotationTransaction
Spring整合Struts2和hibernate
- 项目中使用了泛型接口:dao层接口使用泛型,可以实现对指定的不同实体进行数据库操作,而不用一个实体写一个接口处理。
spring必要maven依赖
- spring核心依赖
- spring-core:基本框架核心工具类,其他spring组件都需要依赖这个包(但好像不加也可以)
- spring-beans:包含配置文件,创建和管理bean
- spring-context
- spring dao依赖(提供jdbcTemplate):spring-jdbc、spring-tx
- spring web依赖:spring-web(好像不加也可以)、spring-webmvc