spring开发
一 spring的核心原理
spring框架的一大目的是为了解耦合,为了达到这个目标,spring的方案是在底层使用对象工厂,即容器,把需要管理的对象添加进来(常是单例),当需要使用这些对象的时候,不需要重新创建,由容器提供已经建立好的对象,直接使用即可。这样,把对象集中统一的进行管理,降低了耦合度,也支持了其他特性的实现。
二核心功能解析与实现
1 IOC 控制反转
把原有自己掌握的东西交由别人控制,把原本自己负责的对象建立与管理,交由spring框架进行建立和控制。
因此,在实现IOC的时候,要对类文件进行一个标注,代表着这个文件里的类,对象将要被spring框架建立与管理。
<bean id="accountController" class="com.example.controller.AccountController">
<property name="accountService" ref="accountService"></property>
</bean>
在这里,bean表示这是spring需要管理的springbean,即运行时会根据这个标签的标注建立和管理对象,id是bean的标识符,在被其他bean调用时使用,class是bean所标记的类文件,也可以直接使用注释的方法进行IOC,
<context:component-scan base-package="com.example"></context:component-scan>
这个标签的意思是将对标识的包进行扫描,包里类文件已经有注释的会被标记为被spring管理。
@Controller(“accountController”) public class AccountController { @Autowired private AccountService accountService; public void transfer(String fromName,String toName,Double money) { try { accountService.transfer(fromName, toName, money); System.out.println("success"); } catch (Exception e) { e.printStackTrace(); System.out.println("fail"); } } }
@Controller就是一个注释,类名上有这个注释的将被spring管理,accountController即是id,如果不指定value值, 那么默认的id值就是类名的名字, 第一个字母小写.
@Component,@Controller :修饰WEB层类 --->web | SpringMVC
@Repository :修饰DAO层类 --->dao
2 DI 依赖注入
如果我们托管的某一个类中存在属性,需要spring在创建该类实例的时候,顺便给这个对象里面的属性进行赋值。 这就是依赖注入。
<bean id="accountController" class="com.example.controller.AccountController">
<!--使用有参构造进行属性的注入,使用<constructor-arg>标签注入-->
<constructor-arg name="userService" ref="userService"></constructor-arg>
<!--set方法注入-->
<property name="accountService" ref="accountService"></property>
<property name="age" value="28"></property> </bean>
其中,使用xml文件的依赖注入如上,使用注解开发的如下。使用@Value,@Autowired,@Qualifier,
@Component("accountService") public class AccountServiceImpl implements AccountService {
@Value("奥巴马")
private String name;
@Autowired//当有多个AccountDao实现类时候, @Autowired会在在Spring容器里面找id为accountDao的对象注入,找不到就报错 private AccountDao accountDao;
Autowired: 自动装配, 如果spring的核心容器中,只有一个该类型的对象,则自动把那个对象注入给当前属性;
* 如果spring的核心容器中,不止有一个该类型的对象,那么就会根据属性名匹配对象的id,匹配上哪个就注入哪个;
* 如果一个都匹配不上,那么我们还可以通过Qualifier指定要注入的对象的id
@Autowired
@Qualifier("userServiceImplAnother")
private UserService userService;
//能够进行自动装配以及手动装配
@Resource(name = "userServiceImpl")
private UserService userService;
@Override public void save() { System.out.println("AccountServiceImpl---save()"); accountDao.save(); } }
3 AOP
面向切面编程。在不修改源码的基础上,对我们的已有方法进行增强。说白了就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,进行增强
AOP中的术语
-
JoinPoint: 连接点(所有可以被增强的方法)
类里面哪些方法可以被增强,这些方法称为连接点. 在spring的AOP中,指的是业务层的类的所有现有的方法。
-
Pointcut: 切入点(具体项目中真正已经被增强的方法)
在类里面可以有很多方法被增强,但是实际开发中,我们只对具体的某几个方法而已,那么这些实际增强的方法就称之为切入点
-
Advice: 通知/增强 (具体用于增强方法的代码)
增强的逻辑、称为增强,比如给某个切入点(方法) 扩展了校验权限的功能,那么这个校验权限即可称之为增强 或者是通知
通知分为:
前置通知: 在原来方法之前执行.
后置通知: 在原来方法之后执行. 特点: 可以得到被增强方法的返回值
异常通知: 目标方法出现异常执行. 如果方法没有异常,不会执行. 特点:可以获得异常的信息
最终通知: 指的是无论是否有异常,总是被执行的。
环绕通知:在方法之前和方法之后执行. 特点:可以阻止目标方法执行
-
Aspect: 切面(所有的通知都是在切面中的)
- 配置aop要先告诉spring
-
基于xml的aop配置
-
<!--全部采用纯配置文件方式--> <!--1. 对UserServiceImpl进行IOC配置--> <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean> <!--2. 对切面类PermissionAspect进行IOC配置--> <bean id="permissionAspect" class="com.itheima.aspect.PermissionAspect"></bean> <!-- 3. 配置AOP <aop:config> 1. 配置切入点 <aop:pointcut id="" expression=""/> id: 切入点的唯一标识 expression: 切入点表达式 2. 配置通知 3. 配置切面关联通知和切入点 --> <aop:config> <!--配置切入点--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/> <aop:pointcut id="pt2" expression="execution(* com.itheima.service.impl.UserServiceImpl.update(..))"/> <!--配置切面--> <aop:aspect id="aspect1" ref="permissionAspect"> <!--配置前置通知--> <aop:before method="checkPermission" pointcut-ref="pt1"></aop:before> <!--配置后置通知--> <aop:after-returning method="printLog" pointcut-ref="pt1"></aop:after-returning> <!--配置异常通知--> <aop:after-throwing method="showException" pointcut-ref="pt1"></aop:after-throwing> <!--配置最终通知--> <aop:after method="showEnd" pointcut-ref="pt1"></aop:after> <!--配置环绕通知--> <aop:around method="showMillions" pointcut-ref="pt2"></aop:around> </aop:aspect> </aop:config> </beans>
配置aop要先将切入点和切面进行ioc配置,在进行aop的配置
- 基于注解的配置
*/ @Service public class UserServiceImpl implements UserService { @Override public void add() { System.out.println("执行添加..."); int num = 10/0; } }
* 1. 注解方式配置切入点 在方法上添加Pointcut注解配置切入点,方法的名字就是切入点的唯一标识 * 2. 配置通知 * 1. 前置通知 Before注解 * 2. 后置通知 AfterReturning注解 * 3. 异常通知 AfterThrowing注解 * 4. 最终通知 After注解 * 5. 环绕通知 Around注解 * 3. 配置切面:在切面类上添加Aspect注解 */ @Component @Aspect public class PermissionAspect { @Pointcut("execution(* com.itheima.service.impl.UserServiceImpl.*(..))") public void pt1(){ } @Pointcut("execution(* com.itheima.service.impl.UserServiceImpl.update(..))") public void pt2(){ } @Before("pt1()") public void checkPermission(){ System.out.println("校验权限..."); } @AfterReturning("pt1()") public void printLog(){ System.out.println("打印日志..."); } @AfterThrowing("pt1()") public void showException(){ System.out.println("出现异常了,请检查..."); } @After("pt1()") public void showEnd(){ System.out.println("执行完毕..."); } //计算目标方法的执行时长 @Around("pt2()") public void showMillions(ProceedingJoinPoint joinPoint){ //1. 记录当前时间 long timeMillis1 = System.currentTimeMillis(); //2. 执行目标方法 try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } //3. 再记录当前时间 long timeMillis2 = System.currentTimeMillis(); //4. 将两次的时间相减,得到方法的执行时长 System.out.println(timeMillis2 - timeMillis1); } }
<!--1. 包扫描--> <context:component-scan base-package="com.itheima"/> <!-- 2. 加载aop的注解驱动 --> <aop:aspectj-autoproxy />
在aspect类中注解切入点,切入面,切入方式
4事务管理(声明式)
声明式的事务管理的思想就是AOP的思想。面向切面的方式完成事务的管理。声明式事务有两种,==xml配置方式和注解方式.==
1、xml
配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
配置事务建议(事务的规则)
<!--配置事务规则--> <tx:advice id="transferAdvice" transaction-manager="transactionManager"> <!--配置事务的属性--> <tx:attributes> <!-- 哪个方法需要使用什么样的事务配置 rollback-for="java.lang.Exception" 什么时候回滚:遇到所有的Exception都会回滚 no-rollback-for="java.lang.NullPointerException" 什么时候不回滚:遇到NullPointerException不回滚 timeout="-1" 事务的超时时间,默认不超时 read-only="false" 是否是只读事务,默认不是只读事务,只读事务只会针对于查询方法,如果是增删改一定不能设置为只读 isolation="" 事务的隔离级别: 目的是为了防止不同事务之间相互影响 1. Read Uncommitted 读取到未提交的事务,在这种隔离级别下,可能发生脏读、不可重复读、幻读 2. Read Committed(Oracle的默认隔离级别) 读取到已提交的事务,在这种隔离级别下,不可能发生脏读,但是可能发生不可重复读和幻读 3. Repeatable Read(mysql的默认隔离级别) 可重复读,在这种隔离级别下不会发生脏读、不可重复读,但是有可能发生幻读 4. Serializable 串行化的,在这种隔离级别下不会发生脏读、不可重复读、幻读 propagation="" 事务的传播行为 - PROPAGATION_REQUIRED:默认值,也是最常用的场景. 如果当前没有事务,就新建一个事务, 如果已经存在一个事务中,加入到这个事务中。 - PROPAGATION_SUPPORTS: 如果当前没有事务,就以非事务方式执行。 如果已经存在一个事务中,加入到这个事务中。 - PROPAGATION_MANDATORY 如果当前没有有事务,就抛出异常; 如果已经存在一个事务中,加入到这个事务中。 保证不在同一个事务里: - PROPAGATION_REQUIRES_NEW 如果当前有事务,把当前事务挂起,创建新的事务但独自执行 - PROPAGATION_NOT_SUPPORTED 如果当前存在事务,就把当前事务挂起。不创建事务 - PROPAGATION_NEVER 如果当前存在事务,抛出异常 --> <tx:method name="transfer" rollback-for="java.lang.Exception" no-rollback-for="java.lang.NullPointerException"/> </tx:attributes> </tx:advice>
配置事务的AOP
<aop:config> <!--声明切入点--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.AccountServiceImpl.transfer(..))"/> <!--绑定切入点和事务--> <aop:advisor advice-ref="transferAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config>
2.注解(推荐)
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
@Transactional(rollbackFor = Exception.class,noRollbackFor = NullPointerException.class) @Service public class AccountServiceIMpl implements AccountService { @Autowired public AccountDao accountDao; @Override public void transfer(String fromName, String toName, Double money) { accountDao.updateAccount(new Account(null, fromName, -money)); // Account account =null; // System.out.println(account.getName()); // int num = 10/0; accountDao.updateAccount(new Account(null, toName, money)); } }
三 其他内容
1
-
-
在spring的IOC容器中创建DataSource对象(可以使用spring内置的DataSource)
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="username" value="root"></property> <property name="password" value="123"></property> <property name="url" value="jdbc:mysql:///day20?characterEncoding=utf8"></property> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> </bean>
-
在spring的IOC容器中创建SqlSessionFactoryBean对象,并且可以指定要配置别名的包或者是加载核心配置文件
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <!--加载mybatis的主配置文件--> <!--<property name="configLocation" value="classpath:SqlMapConfig.xml"></property>--> <!--别名配置的包扫描--> <property name="typeAliasesPackage" value="com.itheima.pojo"></property> </bean>
-
在spring的IOC容器中创建MapperScannerConfigurer对象,用于扫描Dao接口创建代理对象
<bean id="scannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.itheima.dao"></property> </bean>
-
2纯注解方式进行配置
@Configuration @ComponentScan(basePackages = "com.itheima") public class SpringConfig { @Bean public DataSource getDataSource(){ DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setUsername("root"); dataSource.setPassword("123"); dataSource.setUrl("jdbc:mysql:///day20?characterEncoding=utf8"); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); return dataSource; } @Bean public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource){ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); //设置dataSource属性 sqlSessionFactoryBean.setDataSource(dataSource); //设置typeAliasesPackage sqlSessionFactoryBean.setTypeAliasesPackage("com.itheima.pojo"); return sqlSessionFactoryBean; } @Bean public MapperScannerConfigurer getMapperScannerConfigurer(){ MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.itheima.dao"); return mapperScannerConfigurer; } }
3@import import标签
用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration注解, 导入其他配置文件
@Configuration @ComponentScan("com.itheima") @Import({MybatisConfig.class}) public class SpringConfign { }
<import resource="classpath:application-mybatis.xml"/>
4
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_day02 jdbc.username=root jdbc.password=123456
@PropertySource(value = {"classpath:jdbc.properties"}) public class MybatisConfig { @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Value("${jdbc.url}") private String url; @Value("${jdbc.driver}") private String driver; }
<context:property-placeholder location="classpath:jdbc.properties"/>
5
导入spring整合Junit的坐标,在测试类上面标记注解
/** * 包名:com.itheima.test * * @author Leevi * 日期2020-08-09 15:16 * 直接在测试用例中,注入要使用的对象AccountController * 1. 我们自己不创建核心容器,那么我们就应该交给别人(Junit)去创建核心容器 * 1. 引入spring整合Junit的依赖 spring-test * 2. 保证Junit的版本是4.12以及以上的版本 * 3. 在单元测试类上添加RunWith注解 * @RunWith(SpringJUnit4ClassRunner.class) * 4. 指定有Junit创建核心容器的时候,要加载的配置文件/配置类 * @ContextConfiguration(locations = "classpath:applicationContext.xml") 混合开发 * @ContextConfiguration(classes = 配置类名.class) 纯注解开发 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class TestSpring { @Autowired private AccountController accountController; @Test public void testFindAll() throws SQLException { List<Account> accountList = accountController.findAll(); System.out.println(accountList); } }