2021-01-20

一、IoC/DI

1、基本概念:

IoC(Inversion of Control,控制反转),一种设计思想。将设计好的对象交给容器管理,而不是传统的在对象内部直接new控制,实现软件设计的低耦合。

DI(Dependency Injection,依赖注入),是IoC思想的一种实现,容器可以动态地将某个依赖关系注入到组件之中。

对IoC/DI的通俗理解

2、注解(配置类)开发:

新建一个Maven工程,在pom.xml中导入spring-context依赖,发现在Maven Dependencies下存在spring的6个jar包

<dependencies>
  	<!-- 导入spring依赖的jar包,maven网址https://mvnrepository.com/ -->
  	<dependency>
  		<groupId>org.springframework</groupId>
    	<artifactId>spring-context</artifactId>
    	<version>4.3.12.RELEASE</version>
  	</dependency>
  	
	<dependency>
 		<groupId>junit</groupId>
    	<artifactId>junit</artifactId>
    	<version>4.12</version>
 	</dependency>
 </dependencies> 

2.1、向容器中注册组件的方式

a、组件扫描@ComponentScan+标注组件注解@Component

注意由@Component衍生出来的三个注解:@Respority、@Service、@Controller

使用配置文件:
<!-- 包下面只要是被@Component、@Controller、@Service、@Repository标注的类都会被扫描 -->
	<context:component-scan base-package="com.atguigi"/>
	
	<bean class="com.atguigu.beans.Person" id="Person" scope="prototype">
		<property name="name" value="zhangsan"/>
		<property name="age" value="18"/>
	</bean>


使用配置类:
@Configuration // 告诉Spring这是一个配置类
/*
 * @ComponentScan:等价于<context:component-scan/>
 * 		value:指定要扫描的包,默认扫描所有
 * 		excludeFilter = Filter[]:指定扫描时按照什么规则排除那些组件
 * 		includeFilter = Filter[]:指定扫描时只能包含那些组件
 * FilterType.ANNOTATION:按照注解
 * FilterType.ASSIGNABLE_TYPE:按照给定类型
 */
@ComponentScan(value = "com.atguigu", excludeFilters = {
		@Filter(type = FilterType.ANNOTATION, classes = { Controller.class, Service.class }),
		@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = { BookDao.class }) })
public class MainConfig {
}

@Repository
public class BookDao {

}

b、@Configuration+@Bean,导入第三方库的组件
	@Bean("linus")//默认id是方法名,也可以自定义
	public Person person02() {
		return new Person("linus", 45);
	}

@Component和@Bean的区别
如果想将第三方的类变成组件,你又没有没有源代码,也就没办法使用@Component进行自动配置,这种时候使用@Bean就比较合适了。

c、@Import、ImportSelector接口、ImportBeanDefinitionRegisterar接口快速导入组件
@Configuration
// 这里MyImportSelector实现了ImportSelector接口
@Import({ Color.class, Red.class, MyImportSelector.class }) // 快速导入多个或者一个组件,默认id为组件的全限定名
public class MainConfig2 {

	@Bean("person")
	public Person getPerson() {
		System.out.println("给容器中添加Person----");
		return new Person("wangwu", 30);
	}
}

public class MyImportSelector implements ImportSelector {

	/*
	 * 1、返回值就是导入到容器中的组件全限定名。注意不能返回null,可以返回空数组,否则报空指针异常
	 * 2、AnnotationMetadata:当前标注@Import注解的类的所有注解信息(例如该类的@Configuation。)
	 */
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		// Blue和Yellow类通过ImportSelector到容器中注册组件
		return new String[] { "com.atguigu.beans.Yellow", "com.atguigu.beans.Blue" };
	}
}

	@Test // 测试@Import:快速给容器导入一个组件
	public void testImport() {
		// 打印容器中所有组件
		ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
		String[] names = context.getBeanDefinitionNames();
		for (String name : names) {
			System.out.println(name);
		}

	}
d、使用Spring的FactoryBean接口
public class ColorFactoryBean implements FactoryBean<Color> {

	// 返回Color对象,该对象会添加到容器中
	public Color getObject() throws Exception {
		System.out.println("Create...getObject...Bean");
		return new Color();
	}

	public Class<?> getObjectType() {
		return Color.class;
	}

	/*
	 *	返回true:单例,容器中只有这一个对象
	 *	返回false:多例,每次获取都会通过getObject()创建一个新的对象
	 */
	public boolean isSingleton() {
		return true;
	}
}


然后将该bean注册到容器中:
// 实现FactoryBean接口---->容器中注册该实现类---->容器中取
	@Bean
	public ColorFactoryBean getColor() {
		return new ColorFactoryBean();
	}
	
	
	@Test // 测试FactoryBean(工厂Bean)
	public void testFactoryBean() {
		ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);

		// 通过打印发现:工厂bean获取的是调用getObject创建的对象
		Object bean = context.getBean("getColor");
		System.out.println("bean的类型为:" + bean.getClass());

		// 通过打印发现,&符号调用工厂bean对象自身
		Object bean2 = context.getBean("&getColor");
		System.out.println("bean的类型为:" + bean2.getClass());

	}

2.2、@Scope和@Lazy注解

@Configuration
public class MainConfig2 {

	@Bean("person")
	@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
	/*
	 * 默认单例
	 * 		ConfigurableBeanFactory.SCOPE_SIGNLETON:signleton默认单例
	 * 		ConfigurableBeanFactory.SCOPE_PROTOTYPE:prototype多例
	 * 		ConfigurableBeanFactory.SCOPE_REQUEST:request同一次请求创建一个实例
	 * 		ConfigurableBeanFactory.SCOPE_SESSION:session:同一个session创建一个实例
	 通过两条打印测试语句,可以发现:
	 	多实例prototype情况下:IoC容器启动后不会主动创建对象,而是当我们获取(getBean)时才创建对象
	 	单实例signleton情况下:IoC容器启动后主动创建对象,每次获取时都是从容器中拿这同一对象
	 */
	public Person getPerson() {
		System.out.println("给容器中添加Person----");
		return new Person("wangwu", 30);
	}

	 @Bean("person")
	 @Lazy // 懒加载针对单实例的bean,当第一次获取时创建对象
	 public Person person() {
	 	 System.out.println("给容器中添加Person----");
		 return new Person("wangwu", 30);
	 }
}

2.3、Bean的生命周期

/*
 * Bean生命周期:由容器管理
 * 		bean创建---->初始化---->销毁的过程。
 * 我们可以自定义初始化和销毁的方法,容器在bean进行到当前生命周期时会调用这些方法。
 * 
 * 初始化:
 * 		对象创建完成并赋值后,调用初始化方法。
 * 销毁:
 * 		单实例情况下:容器关闭时(context.close())自动销毁
 * 		多实例情况下:容器不会管理这个bean,只能自己手动销毁
 * 
 * 1)、指定初始化和销毁的方法:
 * 			配置文件方式<bean init-method=""  destroy-method="" />
 * 			配置类方式@Bean(initMethod = "", destroyMethod = "")
 * 
 * 2)、bean实现InitializingBean接口完成初始化、实现DisposableBean接口完成销毁
 * 
 * 3)、JSR250规范的 @PostConstruct初始化、@PreDestroy销毁
 * 
 * 4)、BeanPostProcessor:bean的后置处理器,在bean初始化前后进行一些处理工作
 */
@Configuration
public class MainConfigofLifeCycle {

	@Bean(value = "car", initMethod = "init", destroyMethod = "destroy")
	public Car getCar() {
		return new Car();
	}

}

public class Car {

	public Car() {
		System.out.println("car constructor.....");
	}

	public void init() {
		System.out.println("car.......init.....");
	}

	public void destroy() {
		System.out.println("car.......destory.....");
	}
}

public class IoCTest_LifeCycle {
	@Test
	public void testLifeCycle() {
		ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigofLifeCycle.class);
		System.out.println("-------IoC容器创建完成-------");

		String[] definitionNames = context.getBeanDefinitionNames();
		for (String name : definitionNames) {
			System.out.println(name);
		}
	}
}

2.4、@Conditional注解,按一定条件注册组件

/*
	 * @Conditional:按照一定条件进行判断,满足条件时给容器中注册bean。
	 * 		例如:当前系统是Windows,则注册bill;是Linux系统,则注册linus
	 */
	@Bean("bill")
	@Conditional(WindowsCondition.class)
	public Person person() {
		return new Person("Bill Gates", 60);
	}

	@Bean("linus")
	@Conditional(LinuxCondition.class)
	public Person person02() {
		return new Person("linus", 45);
	}


//判断是否为Linux系统
public class LinuxCondition implements Condition {
	/*
	 * ConditionContext:判断条件的上下文环境
	 * AnnotatedTypeMetadata:注解信息
	 */
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		Environment environment = context.getEnvironment();
		String property = environment.getProperty("os.name");
		if (property.contains("linux")) {
			return true;
		}
		return false;
	}

}
public class WindowsCondition implements Condition {
	/*
	 * ConditionContext:判断条件的上下文环境
	 * AnnotatedTypeMetadata:注解信息
	 */
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		Environment environment = context.getEnvironment();
		String property = environment.getProperty("os.name");// 当前系统
		if (property.contains("Windows")) {
			return true;
		}
		return false;
	}

}


	@Test // 测试@Conditional
	public void test03() {
		ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
		Map<String, Person> maps = context.getBeansOfType(Person.class);
		System.out.println(maps);

	}

2.5、属性赋值之@Value、@Autowired、@Qualifier、@Resource

使用配置文件:value注入一个普通值,ref注入一个bean
	<bean class="com.atguigu.beans.Person" id="Person" scope="prototype">
		<property name="name" value="zhangsan"/>
		<property name="age" ref="com.atguigu.Color"/>
	</bean>

使用配置类:
@Repository
public class UserDao {

	@Autowired	//按类型注入JdbcTemplate,前提是容器中必须先注册了
	private JdbcTemplate jdbcTemplate;

	public void insert() {
		String sql = "INSERT INTO tbl_user(username,age) VALUES('lisi',15)";
		jdbcTemplate.update(sql);
	}
}

@Autowired:按类型注入bean,如果同一接口有若干个实现类时,只是注入该接口,就需要使用其他注解。

@Autowired+@Qualifier("名称"):解决上面问题,按名称注入。

@Resource:Java注解规范,效果等同于@Autowired+@Qualifier("名称")

二、AOP:@EnableAspectJAutoProxy+@AspectJ两大核心注解

1、基本概念

AOP(Aspect Oriented Programming,面向切面编程),不同于传统的纵向开发方式,AOP可以在不改变源码情况下,利用代理模式对程序动态统一添加功能。

2、基本开发步骤

以在某个业务逻辑类的目标方法运行前后添加日志为例:

<dependency>
  		<groupId>org.springframework</groupId>
    	<artifactId>spring-aspects</artifactId>
    	<version>4.3.12.RELEASE</version>
</dependency>

//业务逻辑类,在该方法运行前后进行日志打印【aop思想】
public class MathCalculator {

	public int div(int i, int j) {
		System.out.println("div被调用。。。。。。。。。。。。");
		return i / j;
	}
}

//切面类,在业务逻辑类运行前后进行日志输出
@Aspect // 表明是一个切面类
public class LogAspects {
	/* 切入点:就是目标方法
	 * 		如果在本类中引用,直接通过方法名
	 * 		切入点表达式execution
	 */
	@Pointcut("execution(public int com.atguigu.aop.MathCalculator.div(int, int))")
	private void cut() {

	}

	@Before("cut()")
	public void logStart(JoinPoint joinPoint) {
		Object[] args = joinPoint.getArgs();
		System.out.println("除法运行前,参数列表是:" + Arrays.asList(args));
	}

	@After("cut()")
	public void logEnd() {
		System.out.println("除法运行完");
	}

	@AfterReturning(value = "cut()", returning = "result") // 返回值封装进result
	public void logReturn(JoinPoint point, Object result) { // JoinPoint一定出现在形参第一位
		System.out.println("除法正常返回,运行结果为:" + result);
	}

	@AfterThrowing(value = "cut()", throwing = "exception") // 异常信息封装
	public void logException(Exception exception) {
		System.out.println("除法异常,异常信息为:" + exception.getMessage());
	}
}


/*
 * AOP面向切面编程:在程序运行期间动态地将某段代码切入到指定方法/位置进行运行的编程方式。
 * 		底层:动态代理,注解aop步骤如下:
 * 	1、在pom.xml中导入依赖spring-aspects,在Maven Dependencies下发现aspects和waver包
 * 	2、定义一个业务逻辑类,在该类目标方法运行前后进行日志输出
 * 	3、定义一个日志切面,切面里的方法需要动态感知业务逻辑类中目标方法的执行
 * 		通知方法:
 * 			前置通知@Before:在目标方法运行前执行
 * 			后置通知@After:在目标方法运行后执行,无论目标方法是否正常运行
 * 			返回通知@AfterReturing:在目标方法正常运行后执行
 * 			异常通知@AfterThrowing:在目标方法出现异常后执行
 * 			环绕通知@Around:动态代理,手动推进目标方法运行(joinPoint.procced())
 * 	4、给切面类的方法标明通知,并且在切面类上加注解@Aspect
 * 	5、将切面类和业务逻辑类都加入容器
 * 	6、在注解类上开启基于注解的AOP模式【@EnableAspectJAutoProxy】
 */
@Configuration
@EnableAspectJAutoProxy // 等价于配置文件中<aop:aspectj-autoproxy/>
public class MainConfigofAOP {
	@Bean // 注册业务逻辑类
	public MathCalculator calculator() {
		return new MathCalculator();
	}

	@Bean // 注册切面类
	public LogAspects log() {
		return new LogAspects();
	}
}


如果是配置文件开发:
<aop:aspectj-autoproxy/>
 <!-- AOP配置 -->
    <aop:config>
    	<!-- 切入点 -->
    	<aop:pointcut id="myPointCut" expression="execution(* cn.kgc.aop.demo.MyImpl.test01())" />
    	<!-- 切面 -->
    	<aop:aspect ref="myBefore">
    		<!-- 通知 -->
    		<aop:before method="before" pointcut-ref="myPointCut"/>
    		<aop:after method="after" pointcut-ref="myPointCut"/>
    		<aop:after-returning method="returning" pointcut-ref="myPointCut"/>
    			<!-- 注意:after-throwing只有当方法异常退出时才有用 -->
    		<aop:after-throwing method="throwing" pointcut-ref="myPointCut"/>
    	</aop:aspect>
    </aop:config>

三、Spring声明式事务管理:@EnableTransactionManagement+@Transactional两大核心注解

以数据库添加操作为例(UserDao、UserService)

<!-- spring-jdbc依赖 -->
 	<dependency>
 		<groupId>org.springframework</groupId>
    	<artifactId>spring-jdbc</artifactId>
    	<version>4.3.12.RELEASE</version>
 	</dependency>
 	
 	<!-- 数据库驱动依赖 -->
 	<dependency>
 		<groupId>mysql</groupId>
    	<artifactId>mysql-connector-java</artifactId>
    	<version>5.1.6</version>
 	</dependency>
 	
 	<!-- 数据源依赖:这里使用c3p0 -->
 	<dependency>
 		<groupId>c3p0</groupId>
    	<artifactId>c3p0</artifactId>
    	<version>0.9.1.1</version>
 	</dependency>


/*
 * 声明式事务步骤:
 * 		1、pom.xml中导入依赖:spring-jdbc、数据源、数据库驱动
 * 		2、配置数据源dataSource、JdbcTemplate(Spring提供的简化数据库操作工具)操作数据
 * 		3、给方法加上@Transactional表示当前方法是一个事务
 * 		4、在配置类上添加@EnableTransactionManagement,和AOP注解实现相同
 * 		5、配置事务管理器来管理事务
 */
@Configuration
@ComponentScan("com.atguigu.tx")
@EnableTransactionManagement // 开启事务注解功能,等价于<tx:annotation-driven />
public class TxConfig {

	@Bean
	public DataSource dataSource() throws Exception {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setUser("root");
		dataSource.setPassword("root");
		dataSource.setDriverClass("com.mysql.jdbc.Driver");
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
		return dataSource;
	}

	@Bean
	public JdbcTemplate jdbcTemplate() throws Exception {
		// Spring容器会对@Configuration配置类特殊处理,多次调用都只是从容器中找到组件
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
		return jdbcTemplate;
	}

	@Bean // 注册事务管理器
	public PlatformTransactionManager transactionManager() throws Exception {
		return new DataSourceTransactionManager(dataSource());
	}
}


@Repository
public class UserDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	public void insert() {
		String sql = "INSERT INTO tbl_user(username,age) VALUES('lisi',15)";
		jdbcTemplate.update(sql);
	}
}
@Service
public class UserService {
	@Autowired
	private UserDao userDao;

	@Transactional // 标注该方法是一个事务
	public void insertUser() {
		userDao.insert();
		System.out.println("--------insert user------");

		int i = 10 / 0;// 测试事务
	}
}

	@Test
	public void test() {
		ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);

		UserService service = (UserService) context.getBean("userService");
		service.insertUser();
	}

四、两种开发方式比较的说明

注解开发只是在配置类上添加@Configuration注解,它表明该类就等同于原来的applicationContext.xml配置文件了。全面取代了原有的配置形式,将原来</>标签换成了注解。例如:context:component-scan/换成了@Component-Scan、aop:aspectj-autoproxy/换成了@AspectJ等等,只是简化了配置,但是开发流程是不变的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值