IOC:Inversion of Control (控制反转)。
描述的事情:Java开发领域对象的创建和管理问题。
传统的开发方式:在类A依赖类B的时候,往往会在类A中new一个B对象。
public class A{
B b = new B();
b.hello();
}
IOC思想下的开发方式:不用自己去new对象,而是由IOC容器去实例化对象并进行管理。我们需要用到哪个对象,去IOC容器中拿取。
此时,创建和管理对象的权力(控制),交给了外部环境(反转)。
// IOC容器
Map map = new HashMap<>();
// 往IOC容器中放入已经实例化好的对象
map.put("beanName", instance);
// 需要使用IOC容器中的对象时
map.get("beanName");
对象之间的耦合问题。
传统的开发方式:
// 定义操作数据库的接口类
public interface AccountDao {}
// 利用传统JDBC实现操作数据库的接口
public class JdbcAccountDaoImpl implements AccountDao {}
// 业务逻辑层
public class TransferServiceImpl implements TransferService {
// 此时操作数据库的方式为 传统的JDBC
private AccountDao accountDao = new JdbcAccountDaoImpl();
}
// 若此时抛弃传统JDBC实现操作数据库 改用Mybatis实现操作数据库
public class MybatisAccountDaoImpl implements AccountDao {}
// 需要将业务逻辑层代码改为
public class TransferServiceImpl implements TransferService {
// 此时操作数据库的方式为 Mybatis
private AccountDao accountDao = new MybatisAccountDaoImpl();
}
// 若业务层代码中有“成千上万”个地方需要更换new的对象,此时的工作量会巨大。不符合Java面向接口编程的习惯。
IOC思想下的开发方式:
// 业务逻辑层
public class TransferServiceImpl implements TransferService {
// 此时只定义需要用到的接口 不去new实现
private AccountDao accountDao;
}
DI:Dependancy Injection(依赖注入)。
IOC和DI描述都是对象实例化及依赖关系维护这件事,只是站在的角度不同。IOC是站在对象的角度,将对象实例化和管理的权限交给了容器。DI是站在容器的角度,容器会把对象依赖的其他对象注入。
pom.xml
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.1.12.RELEASEversion>
dependency>
resources\applicationContext.xml
<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="accountDao" class="com.demo.dao.impl.JdbcAccountDaoImpl"
scope="singleton" init-method="init" destroy-method="destroy">
<constructor-arg index="0" ref="connectionUtils">constructor-arg>
<constructor-arg index="1" value="zhangsan">constructor-arg>
<constructor-arg index="2" value="1">constructor-arg>
<constructor-arg index="3" value="100.3">constructor-arg>
bean>
<bean id="transferService" class="com.demo.service.impl.TransferServiceImpl">
<property name="AccountDao" ref="accountDao">property>
<property name="myArray"><array><value>1value><value>2value>array>property>
<property name="myMap"><map><entry key="1" value="1"/><entry key="2" value="2"/>map>property>
<property name="mySet"><set><value>1value><value>2value>set>property>
<property name="myProperties">
<props><prop key="1">value1prop><prop key="2">value2prop>props>
property>
bean>
<bean id="connectionUtils" class="com.demo.utils.ConnectionUtils"/>
<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory"
factory-method="getInstanceStatic">
<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory"/>
<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory"
factory-bean="createBeanFactory" factory-method="getInstance"/>
beans>
public class CreateBeanFactory {
// 在静态方法中实例化对象 将自己 new 的对象要放到容器中
public static ConnectionUtils getInstanceStatic() {
return new ConnectionUtils();
}
// 实例化方法
public ConnectionUtils getInstance() {
return new ConnectionUtils();
}
}
JavaEE方式
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
JavaSE方式
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:applicationContext.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
resources\applicationContext.xml
<context:component-scan base-package="com.demo"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
beans>
xml形式 | 对应的注解形式 |
---|---|
bean标签 | @Component:注解加载类上。bean的id默认为类名的首字母小写。 @Controller:用于控制层 @Service:用于服务层 @Repository:用于Dao层 |
scope属性 | @Scope(“prototype”),默认单例,注解加在类上 |
init-method属性 | @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 |
destroy-method属性 | @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 |
DI依赖注入的方式 | @Autowired org.springframework.beans.factory.annotation.Autowired 默认按照类型注入。如果一个类型的实现方式有很多需要配合@Qualififier去告诉Spring配置哪个对象。 |
@Configuration
@ComponentScan({"com.demo"})
public class SpringConfig {
}
JavaEE方式
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
JavaSE方式
<context-param>
<param-name>contextClassparam-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContextparam-value>
context-param>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>com.lagou.edu.SpringConfigparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
注解 | 描述 |
---|---|
@PropertySource | 引⼊外部属性配置⽂件 |
@Import | 引⼊其他配置类 |
@Value | 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息 |
@Bean | 将⽅法返回对象加⼊ SpringIOC 容器 |
@Component
@PropertySource({"classpath:jdbc.properties"})
public class DataSourceConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
// 自己 new 一个对象 放入 IOC 容器
@Bean
public DruidDataSource createDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
概念:ApplicationContext 容器的默认行为是在启动服务的时候将所有 singleton 类型的 bean 提前进行实例化。将 lazy-init 设置为 true,bean 将不会再 ApplicationContext 启动时提前被实例化,而是第一次向容器通过 getBean 索取 bean 时实例化。此属性对 scope=“pototype” 的 bean 不生效。
<bean id="lazyBean" calss="cn.demo.LazyBean" lazy-init="false" />
<beans default-lazy-init="true">beans>
特殊情况:存在两个bean。bean2 设置 lazy-init = true;bean1 设置 lazy-init = false,此时 bean2 会延迟加载。但是若 bean2 被 bean1 引用,在实例化 bean1 的同时,bean2也会被实例化。
概念:Spring 有两种bean:一种普通的 bean,一种工厂 bean。我们可以借助 FactoryBean 接口生成某一类型的 bean 实例。
// 利用 ResultBean 创建 Result 类型的 bean
public class ResultBean implements FactoryBean<Result> {
@Override
public Result getObject() throws Exception {
Result result = new Result();
return result;
}
@Override
public Class<?> getObjectType() {
return Result.class;
}
}
// 获取 Result 类型的 bean
applicationContext.getBean("resultBean");
// 获取 ResultBean 工厂 bean
applicationContext.getBean("&resultBean");
1)创建 beanFactory。可以实现 BeanFactoryPostProcessor 接口拿到 beanFactory。
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("MyBeanFactoryPostProcessor:" + beanFactory);
}
}
2)初始化BeanPostProcessor。
@Component
public class MyBeanPostprocessor implements BeanPostProcessor {
public MyBeanPostprocessor() {
System.out.println("2)初始化MyBeanPostprocessor");
}
}
3)bean的构造方法。
public class LifeCycle implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
public LifeCycle() {
System.out.println("3)LifeCycle的构造方法");
}
}
4)BeanNameAware 接口。
@Override
public void setBeanName(String name) {
System.out.println("4)BeanNameAware:" + name);
}
5)BeanFactoryAware 接口。
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("5)BeanFactoryAware:" + beanFactory);
}
6)ApplicationContextAware 接口。
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("6)ApplicationContextAware:" + applicationContext);
}
7)BeanPostProcessor 接口的 postProcessBeforeInitialization 方法。
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("7)postProcessBeforeInitialization:" + beanName);
return bean;
}
8)@PostConstruct 注解标注的方法。
@PostConstruct
public void PostConstruct() {
System.out.println("8)PostConstruct");
}
9)InitializingBean 接口的 afterPropertiesSet 方法。
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("9)InitializingBean:afterPropertiesSet");
}
10)xml 中 init-method 方法。
public void initMethod() {
System.out.println("10)initMethod");
}
11)BeanPostProcessor 接口的 postProcessAfterInitialization 方法。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("11)postProcessAfterInitialization:" + beanName);
return bean;
}
12)ApplicationContext 的创建。
public void TestLifeCycle() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
System.out.println("12)TestLifeCycle:" + context);
context.close();
}
13)@PreDestroy 注解标注的方法。
@PreDestroy
public void PreDestroy() {
System.out.println("13)PreDestroy");
}
14)DisposableBean 接口的 destroy 方法。
@Override
public void destroy() throws Exception {
System.out.println("14)DisposableBean:destroy");
}
15)xml 中 destroy-method。
public void destroyMethod() {
System.out.println("15)destroyMethod");
}
AOP:Aspect Oriented Programming(面向切面编程)。
OOP:Object Oriented Programming(面向对象编程)。
AOP 是 OOP 的延续。OOP 三大特征:封装,继承,多态。是一种垂直继承体系。
OOP开发方式:
Public class Animal {
public void eat(){
System.out.println("eating...");
}
public void run(){
System.out.println("running...");
}
}
Public class Cat extends Animal {
// 此时不需要额外的再书写 eat 和 run 方法 解决了大多数代码重复问题
// 然而如果每个 System.out.println() 前后需要增加大量重复的代码 此时 OOP就不能解决代码重复问题
// 而且将业务代码和 日志或性能监控代码混杂在一起,代码臃肿,维护不方便
}
AOP优化方式:
public class ProxyFactory {
// JDK 动态代理
public Object getJdkProxy(Object obj) {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return getObject(method, args, obj);
}
});
}
private Object getObject(Method method, Object[] args, Object obj) throws SQLException, IllegalAccessException, InvocationTargetException {
// 此处可以编写横切逻辑代码...
Object result = null;
try {
result = method.invoke(obj, args);
// 此处可以编写横切逻辑代码...
} catch (Exception e) {
// 此处可以编写横切逻辑代码...
throw e;
}
return result;
}
// cglib 动态代理
public Object getCglibProxy(Object obj) {
return Enhancer.create(obj.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return getObject(method, objects, obj);
}
});
}
}
名词 | 解释 |
---|---|
连接点 Joinpoint | 可以插入横切逻辑代码的地方 |
切入点 Pointcut | 已经插入横切逻辑代码的地方 |
通知/增强 Advice | 横切逻辑代码 |
目标对象 Target | 被代理的对象 |
代理 Proxy | 代理对象 |
织入 Weaving | 插入横切逻辑代码的过程 |
切面 Aspect | 横切逻辑代码整合后的类 |
默认情况下,Spring 会根据被代理对象是否实现了接口来选择是否使用 JDK动态代理 还是 cglib动态代理。当没有实现接口的时候选择 cglib 动态代理,当实现接口的时候会选择 JDK官方的代理技术,我们也可以通过配置的方式让 Spring 强制使用 cglib。
pom.xml
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.1.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.4version>
dependency>
resources\applicationContext.xml
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
<aop:config>
<aop:aspect id="logAspect" ref="logUtils">
<aop:pointcut id="pt1" expression="execution(public int com.demo.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String, int))"/>
<aop:before method="beforeMethod" pointcut-ref="pt1"/>
<aop:after method="afterMethod" pointcut-ref="pt1"/>
<aop:after-returning method="successMethod" returning="rtValue" pointcut-ref="pt1"/>
<aop:after-throwing method="exceptionMethod" pointcut-ref="pt1"/>
<aop:around method="aroundMethod" pointcut-ref="pt1"/>
aop:aspect>
aop:config>
<aop:aspectj-autoproxy expose-proxy="true"/>
@Component
@Aspect
public class LogUtils {
@Pointcut("execution(* com.demo.service.impl.TransferServiceImpl.*(..))")
public void pt1() {
}
@Before("pt1()")
public void beforeMethod(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
// 可以拿到 所有传递进来的参数
for (Object arg : args) {
System.out.println(arg);
}
System.out.println("业务逻辑开始执行之前执行.......");
}
// finally
@After("pt1()")
public void afterMethod() {
System.out.println("业务逻辑结束时执行,无论异常与否都执行.......");
}
@AfterThrowing("pt1()")
public void exceptionMethod() {
System.out.println("异常时执行.......");
}
@AfterReturning(value = "pt1()",returning = "rtValue")
public void successMethod(Object rtValue) {
System.out.println(rtValue);
System.out.println("业务逻辑正常返回时执行.......");
}
// @Around("pt1()")
public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("在执行方法之前");
try {
// 控制原有业务逻辑是否执行
proceedingJoinPoint.proceed();
System.out.println("在执行方法之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("有异常时执行");
} finally {
System.out.println("最终执行");
}
}
}
@EnableAspectJAutoProxy
public class SpringConfig {
}
特性 | 描述 |
---|---|
原子性 atomicity | 事务是一个不可分割的工作单位,事务中的操作要么同时发生,要么同时失败。 |
一致性 consistency | 事务必须使数据库从一个一致性状态变换为另一个一致性状态。不会存在一个中间状态。 |
隔离性 isolation | 当多个用户并发产生事务的时候,每个事务不能被其他事务干扰,事务相互之间隔离。 |
持久性 durability | 事务一旦提交,改变就是永久性的。 |
错误情况 | 描述 |
---|---|
脏读 | 事务A 读取到了 事务B **未提交 **的数据。 |
幻读 | 事务A 读取到了 事务B insert 或 delete 提交后 的数据。导致前后读取的条数不一致。 |
不可重复读 | 事务A 读取到了 事务B update提交后 的数据。导致前后读取的内容不一致。 |
隔离级别 | 描述 |
---|---|
串行化 Serializable | 最高。避免上述三种情况。 |
可重复度 Repeatable Read | 第二。MySQL的默认隔离级别。可避免脏读和不可重复读。update时会有行锁。 |
读已提交 Read Committed | 第三。可避免脏读。 |
读未提交 Read Uncommitted | 第四。无法避免上述三种情况。 |
事务往往定义在 service 层。如果 service 层方法 A 调用了另一个 service 层方法 B,A 和 B 本身都被添加了事务,那么 A 调用 B 的时候,就需要进行一些协商,这就叫做事务的传播行为。A 调用 B,我们需要站在 B 的角度来定义事务的传播行为。
1)PROPAGATION_REQUIRED:B 如果没有事务,就新建一个事务。如果 A 已经存在一个事务,就加入到A的事务中。这是常见的选则。用于增删改查。
2)PROPAGATION_SUPPORTS:B 支持 A 的事务,如果 B 没有事务,就以非事务方式执行。常用于查询。
pom.xml
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.1.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.4version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.1.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.1.12.RELEASEversion>
dependency>
resources/applicationContext.xml
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT"/>
<tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
tx:attributes>
tx:advice>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource"/>
bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
resources/applicationContext.xml
<tx:annotation-driven transaction-manager="transactionManager"/>
@Service("transferService")
public class TransferServiceImpl implements TransferService {
@Override
// 配置声明式事务 注解里面有默认值
@Transactional
public void transfer() throws Exception {
}
}
@Configuration
@ComponentScan({"com.demo"})
@EnableTransactionManagement
public class SpringConfig {
}
// 自己 new 一个对象 放入 IOC 容器
@Bean
public DruidDataSource createDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
// JdbcTemplate 放入容器
@Bean
public JdbcTemplate createJdbcTemplate(DruidDataSource druidDataSource) {
return new JdbcTemplate(druidDataSource);
}
// DataSourceTransactionManager 声明式事务放入容器
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DruidDataSource druidDataSource) {
return new DataSourceTransactionManager(druidDataSource);
}
参考git:https://gitee.com/zhangyizhou/learning-spring-demo.git
1.自定义 ioc 和 aop 项目:lagou-transfer
1.spring ioc 纯xml项目:lagou-transfer-iocxml
2.spring ioc xml +注解项目;aop 纯xml项目:lagou-transfer-ioc-xml-anno
3.spring ioc+aop 纯注解项目:lagou-transfer-ioc-anno
4.spring 声明周期项目:lagou-spring-life-cycle
5.声明式事务项目:lagou-transfer-transaction