IoC(Inversion of Control,控制反转),一种设计思想。将设计好的对象交给容器管理,而不是传统的在对象内部直接new控制,实现软件设计的低耦合。
DI(Dependency Injection,依赖注入),是IoC思想的一种实现,容器可以动态地将某个依赖关系注入到组件之中。
对IoC/DI的通俗理解
新建一个Maven工程,在pom.xml中导入spring-context依赖,发现在Maven Dependencies下存在spring的6个jar包
org.springframework
spring-context
4.3.12.RELEASE
junit
junit
4.12
注意由@Component衍生出来的三个注解:@Respority、@Service、@Controller
使用配置文件:
使用配置类:
@Configuration // 告诉Spring这是一个配置类
/*
* @ComponentScan:等价于
* 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 {
}
@Bean("linus")//默认id是方法名,也可以自定义
public Person person02() {
return new Person("linus", 45);
}
@Component和@Bean的区别
如果想将第三方的类变成组件,你又没有没有源代码,也就没办法使用@Component进行自动配置,这种时候使用@Bean就比较合适了。
@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);
}
}
public class ColorFactoryBean implements FactoryBean {
// 返回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());
}
@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);
}
}
/*
* Bean生命周期:由容器管理
* bean创建---->初始化---->销毁的过程。
* 我们可以自定义初始化和销毁的方法,容器在bean进行到当前生命周期时会调用这些方法。
*
* 初始化:
* 对象创建完成并赋值后,调用初始化方法。
* 销毁:
* 单实例情况下:容器关闭时(context.close())自动销毁
* 多实例情况下:容器不会管理这个bean,只能自己手动销毁
*
* 1)、指定初始化和销毁的方法:
* 配置文件方式
* 配置类方式@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);
}
}
}
/*
* @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 maps = context.getBeansOfType(Person.class);
System.out.println(maps);
}
使用配置文件:value注入一个普通值,ref注入一个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(Aspect Oriented Programming,面向切面编程),不同于传统的纵向开发方式,AOP可以在不改变源码情况下,利用代理模式对程序动态统一添加功能。
以在某个业务逻辑类的目标方法运行前后添加日志为例:
org.springframework
spring-aspects
4.3.12.RELEASE
//业务逻辑类,在该方法运行前后进行日志打印【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 // 等价于配置文件中
public class MainConfigofAOP {
@Bean // 注册业务逻辑类
public MathCalculator calculator() {
return new MathCalculator();
}
@Bean // 注册切面类
public LogAspects log() {
return new LogAspects();
}
}
如果是配置文件开发:
以数据库添加操作为例(UserDao、UserService)
org.springframework
spring-jdbc
4.3.12.RELEASE
mysql
mysql-connector-java
5.1.6
c3p0
c3p0
0.9.1.1
/*
* 声明式事务步骤:
* 1、pom.xml中导入依赖:spring-jdbc、数据源、数据库驱动
* 2、配置数据源dataSource、JdbcTemplate(Spring提供的简化数据库操作工具)操作数据
* 3、给方法加上@Transactional表示当前方法是一个事务
* 4、在配置类上添加@EnableTransactionManagement,和AOP注解实现相同
* 5、配置事务管理器来管理事务
*/
@Configuration
@ComponentScan("com.atguigu.tx")
@EnableTransactionManagement // 开启事务注解功能,等价于
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等等,只是简化了配置,但是开发流程是不变的。