Spring 注解版
@Bean — 组件注册
对于一个普通的bean: Person
package com.spring.annotation.bean;
public class Person {
private String name;
private Integer age;
getter() setter()...
}
-
传统方式–配置文件
applicationContext.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="person" class="com.spring.annotation.bean.Person"> <property name="name" value="Sairo"></property> <property name="age" value="18"></property> </bean> </beans>
-
测试类
采用配置文件的方式时创建ClassPathXmlApplicationContext对象
public class XMLTest { @Test public void test00() { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); Person person = (Person) context.getBean("person"); System.out.println(person); } }
-
注解方式–配置类
当采用注解方式时,需要编写一个用@Configutation标记的类作为配置类,用来取代配置文件
MainConfig
/** * 配置类代替了配置文件 * @Configuration: 告诉Spring这是一个配置类 */ @Configuration public class MainConfig { /** * @Bean: 取代了 <bean><bean/> 标签 * 给容器中注册一个bean, id默认是方法名 */ @Bean public Person person() { return new Person(); } }
-
测试类
采用注解方式时,要创建AnnotationConfigApplicationContext实例,传入配置类的class对象
public class AnnotationTest { @Test public void test00() { ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class); Person person = (Person) context.getBean("person"); System.out.println(person); } }
-
更改bean在容器中的名字
-
修改配置类中的方法的名称
public Person person01() { return new Person("LiSi", 20); }
-
在@Bean注解中通过value属性指定名字
@Bean(value="person01") public Person person() { return new Person("LiSi", 20); }
-
测试
@Test public void test01() { ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class); Person person = (Person) context.getBean("person"); System.out.println(person); // 查看bean在Spring容器中的名字 String[] beanNames = context.getBeanNamesForType(Person.class); for (String beanName : beanNames) { System.out.println(beanName); } }
-
@ComponentScan — 包扫描
包扫描是用于扫描指定包(及其子包)下的标注了@Controller, @Service, @Repository, @Component注解的类,并将它们加载到Spring容器中
-
配置文件方式
<!-- 包扫描, 用于扫描指定包(及其子包)下的标注了@Controller, @Service, Repository, @Component注解的类, 将其加载到容器中 --> <context:component-scan base-package="com.lymboy.spring" ></context:component-scan>
-
注解方式
标注在MainConfig配置类上(头部)
@ComponentScan(value = "com.lymboy.spring")
-
测试
public class IOCTest { private ApplicationContext applicationContext; @Before public void init() { applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); } @Test public void test01() { int count = applicationContext.getBeanDefinitionCount(); String[] names = applicationContext.getBeanDefinitionNames(); System.out.println(count+": "); for (String name : names) { System.out.println(name); } } }
@ComponentScan属性详解
-
value: 用来指定扫描的包
@ComponentScan(value = "com.lymboy.spring")
-
useDefaultFilters: 使用默认扫描规则, 全部扫描
@ComponentScan(value = "com.lymboy.spring" useDefaultFilters = false) // 关闭
-
excludeFilters: 用来排除不需要的类
Filter[] excludeFilters() default {};
@ComponentScan(value = "com.lymboy.spring", excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}) })
参数是一个 @Filter数组
- type: 指定过滤方式 默认是 FilterType.ANNOTATION(注解)方式 (FilterType type() default FilterType.ANNOTATION;), 选择按注解类型过滤时, 在value中指定的包(及其子包)下的所有在classes(下面的这个属性)中指定的注解都不会被扫描
- classes数组: 指定过滤的类型 . 如果是按注解过滤, 则classes中填注解类型
-
includeFilters: 仅加载指定的类, 首先要禁用默认扫描规则, 使用方法与上面的excludeFilters相同, 作用相反
@ComponentScan(value = "com.lymboy.spring", useDefaultFilters = false, includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}) })
过滤规则 FilterType
用来指定包扫描的过滤规则
public enum FilterType {
// 注解方式 常用
ANNOTATION,
// 按照给定的类型 常用
ASSIGNABLE_TYPE,
// 不常用
ASPECTJ,
// 使用正则表达式
REGEX,
// 用户自定义
CUSTOM
}
-
FilterType.ANNOTATION :
-
FilterType.ASSIGNABLE_TYPE : 与 FilterType.ANNOTATION 相似, 前者在classes属性中填 类的类型, 后者填注解的类型
-
FilterType.REGEX : 通过正则表达式过滤
@ComponentScan(value = "com.lymboy.spring", useDefaultFilters = false, includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = {".*Service"}) })
-
FilterType.CUSTOM : 通过实现 org.springframework.context.annotation.FilterType 接口, 自定义规则
MyTypeFilter :自定义的类,实现了 FilterType 接口
@ComponentScan(value = "com.lymboy.spring", useDefaultFilters = false, includeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) })
package com.lymboy.spring.annotation.config;
public class MyTypeFilter implements TypeFilter {
/**
* @param metadataReader: 用来读取当前正在扫描的类的信息
* @param metadataReaderFactory: 用来访问其他类的信息
* @return
* @throws IOException
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
Resource resource = metadataReader.getResource();
String filename = resource.getFilename();
if (filename.contains("Service")) {
return true;
}
return false;
}
}
过滤方式 | 表达格式 |
---|---|
FilterType.ANNOTATION | classes 属性中填入需过滤的注解的class对象 |
FilterType.ASSIGNABLE_TYPE | classes属性中填入需过滤的类的class对象 |
FilterType.REGEX | pattern 属性中填入对应的正则表达式 |
FilterType.CUSTOM | 实现 FilterType 接口 |
@Scope — 作用域
-
@Scope : 用于设定 bean 的作用范围, 即单实例还是多实例
- @scope 注解添加在@Bean注解添加的地方, 一般是bean上
@Configuration public class MainConfig2 { /** * singleton: 单实例(默认值) 所有的getBean() 返回的都是同一个bean实例 * prototype: 多实例 每次调用getBean() 都返回一个新的 bean 实例 * request: web环境下, 每一个请求创建一个request * session: web环境下, 每一个请求创建一个session */ @Scope(scopeName = "singleton") // scopeName属性 可以替换为 value, 两个完全一致 @Bean public Person person() { return new Person("张三", 25); } }
测试类
@Test public void test02() { applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); Person bean1 = applicationContext.getBean(Person.class); Person bean2 = applicationContext.getBean(Person.class); System.out.println(bean1 == bean2); }
结果
-
singleton : 只创建一次对应的bean对象, 且默认bean的实例是在 IOC 容器创建完成时就已经创建了, 以后每次调用 getBean() 方法在容器中直接获取
测试
// 在配置类中配置 @Scope(scopeName = "singleton") // scopeName属性 可以替换为 value, 两个完全一致 @Bean public Person person() { System.out.println("Person 创建了..."); return new Person("张三", 25); }
@Test public void test03() { applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); System.out.println("IOC 容器已经创建完了..."); applicationContext.getBean(Person.class); applicationContext.getBean(Person.class); }
可见IOC容器创建完成时bean实例也已经创建完成了, 且每次调用getBean() 方法都是直接在 IOC 容器中获取的,没有调用创建实例方法
如果不想在容器创建的时候创建 bean 实例, 可以使用@Lazy 标记相关的bean为懒加载模式, 即首次调用getBean() 方法时才真正的创建 bean实例
-
prototype : 每次调用 getBean() 方法都创建一个新的 bean实例, 与IOC 容器创建无关
@Scope(scopeName = "prototype") // scopeName属性 可以替换为 value, 两个完全一致 @Bean public Person person() { System.out.println("Person 创建了..."); return new Person("张三", 25); }
@Test public void test03() { applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); System.out.println("IOC 容器已经创建完了..."); Person bean1 = applicationContext.getBean(Person.class); Person bean2 = applicationContext.getBean(Person.class); System.out.println(bean1 == bean2); }
@Lazy — 懒加载/延迟加载
懒加载针对单实例 bean
懒加载使得单实例的 bean 在IOC 容器 创建时不自动创建, 而是每次使用到相关的 bean 时容器再去创建.
实例
@Scope(scopeName = "singleton") // scopeName属性 可以替换为 value, 两个完全一致
@Bean
@Lazy
public Person person() {
System.out.println("Person 创建了...");
return new Person("张三", 25);
}
@Test
public void test03() {
applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("IOC 容器已经创建完了...");
}
可见, 当标注 @Lazy 注解后, 单实例的bean 不再在 IOC容器创建时也一起创建了
@Conditional — 按条件注册
可以标注在类上或方法上
按照一定条件在容器中注册 bean
@Conditional(WindowsCondition.class)
参数是实现了 org.springframework.context.annotation.Condition 接口的类对象
判定是否是 windows 操作系统
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
if (osName.contains("Windows")) {
return true;
}
return false;
}
}
@Conditional(WindowsCondition.class)
@Bean (value = "bill")
public Person person01() {
return new Person("Bill Gates", 62);
}
@Test
public void test04() {
applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
String[] names = applicationContext.getBeanNamesForType(Person.class);
for (String name : names) {
System.out.println(name);
}
Map<String, Person> person = applicationContext.getBeansOfType(Person.class);
System.out.println(person);
Environment environment = applicationContext.getEnvironment();
String os = environment.getProperty("os.name");
System.out.println(os);
}
ps: 可以通过修改 vm参数 ‘伪装’ 成 Linux操作系统
-Dos.name=Linux
如果 @Conditional 注解添加在配置类上, 那么只有当满足@Conditional 的条件时 配置类中的配置才会生效
@Import — 组件导入
对于以前的给容器中添加组建的方式有三种
- 包扫描带有 @Controller, @Service, @Repository, @Component 注解的类, 但是这些类一般是我们自己写的
- @Bean : 注解一般用于导入第三方的类, 但是美导入一个组件到要在配置类中写一个方法, 显得很繁杂,臃肿
- @Import : 快速导入一个组件
- 直接在配置类上标记 @Import 注解, 参数是要导入的组建的class对象的数组
- 实现 ImportSelector 接口, 返回 需导入的组建的类的全类名数组
- 实现 ImportBeanDefinitionRegistrar 接口
**第一种方法: **
public class Color {
}
@Test
public void test05() {
applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
**第二种方法: **
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.lymboy.spring.annotation.bean.Blue", "com.lymboy.spring.annotation.bean.Red"};
}
}
@Test
public void test05() {
applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
Blue blue = applicationContext.getBean(Blue.class);
System.out.println(blue);
}
第三种方法 :
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 当前类的注册信息
* @param registry BeanDefinition注册类,用于对IOC容器中的bean增删改查
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean red = registry.containsBeanDefinition("com.lymboy.spring.annotation.bean.Red");
boolean color = registry.containsBeanDefinition("com.lymboy.spring.annotation.bean.Color");
if (red && color) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}
@Test
public void test06() {
applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
FactoryBean — 组件注册
用于给容器中注册bean
需要实现 FactoryBean 接口
public class ColorFactoryBean implements FactoryBean<Color> {
/**
* 返回Color对象, 该对象会添加到容器中
* @return
* @throws Exception
*/
@Override
public Color getObject() throws Exception {
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
/**
* 设定是否是单例模式, 是设定, 不是判断
* true: 单实例
* false: 多实例
* @return
*/
@Override
public boolean isSingleton() {
return false;
}
}
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
@Test
public void test07() {
applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
Object color0 = applicationContext.getBean("colorFactoryBean");
System.out.println(color0);
}
虽然 getBean 的参数是 colorFactoryBean 但实际上返回的是 Color 对象, 因为容器会自动调用 FactoryBean 的 getObject 方法, 如果要真的返回 colorFactoryBean 实例, 只要在 getBean 参数前加 ‘&’ 符号就能返回对应的 FactoryBean 实例了 (至于为什么要添加’’&’ 这是因为Spring本身的设置)
// org.springframework.beans.factory.BeanFactory
String FACTORY_BEAN_PREFIX = "&";
@Test
public void test07() {
applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
Object color0 = applicationContext.getBean("&colorFactoryBean");
System.out.println(color0);
}
@Bean — 生命周期
以前都是容器自动管理 bean 的创建与销毁, 但是我们也可以自己管理 bean 的创建与销毁
-
配置文件方式
在 bean 标签中添加 init-method 和 destroy-method 属性
<bean id="person" class="com.lymboy.spring.annotation.bean.Person" scope="singleton" init-method="" destroy-method=""> <property name="name" value="Sairo"></property> <property name="age" value="18"></property> </bean>
-
注解方式
在 @Bean 注解中添加相关属性
Car
public class Car { public void init() { System.out.println("Car Created..."); } public void destroy() { System.out.println("Car has been destroyed..."); } }
配置类
@Configuration public class MainConfigOfLifeCycle { @Bean(initMethod = "init", destroyMethod = "destroy") public Car car() { return new Car(); } }
测试
public class IOCTest_LifeCycle { private AnnotationConfigApplicationContext applicationContext; @Test public void test00() { applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); applicationContext.getBean("car"); applicationContext.close(); } }
结果
可见, 当在 @Bean 中标注 initMethod 后, 容器创建该 bean 时会主动调用相关的初始化方法
同理, 当容器关闭, 即 bean 要被销毁的时候也会调用相关的销毁方法
**PS: ** 原始的 ApplicationContext 并没有定义 close() 方法, 其具体的实现类才有
注意:
以上的效果只是在单例情况下才有效, 对于多实例 bean, IOC容器在创建完 bean 后就不会管理这个 bean了, 所以当容器关闭时不会调用销毁方法,而且因为是多实例的, 只有当调用 getBean() 方法是才会创建 bean, 即调用 initMethod 标记的方法
@Scope("prototype")
@Bean(initMethod = "init", destroyMethod = "destroy")
public Car car() {
return new Car();
}
InitializingBean, DisposableBean
通过使 bean 实现 InitializingBean, DisposableBean 接口, 并实现相关的方法, 容器会自动调用 初始化和销毁方法, 同样也区分单例和多例
@Component
public class Cat implements InitializingBean, DisposableBean {
public Cat() {
System.out.println("Cat contructor...");
}
/**
* 用于销毁 bean
* @throws Exception
*/
@Override
public void destroy() throws Exception {
System.out.println("Cat destroy...");
}
/**
* 当 bean 的所有属性(构造器) 完成后执行此方法, 用于初始化 bean
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Cat afterPropertiesSet...");
}
}
配置类加包扫描
测试
@Test
public void test01() {
applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
applicationContext.close();
}
可见, 当使用实现接口这种方法时, 我们就不用再去显示指定初始化和销毁方法了, 容器会自动调用
@PostConstruct, @PreDestroy
直接标注在类的 初始化/销毁 方法上
**@PostConstruct: ** 在对象创建之后,所有属性赋好值后调用, 用于对象的一些初始化操作
@PreDestroy: 容器移除对象之前调用
@Component
public class Dog {
public Dog() {
System.out.println("Dog Contructor...");
}
@PostConstruct
public void init() {
System.out.println("Dog created...");
}
@PreDestroy
public void destroy() {
System.out.println("Dog destroy...");
}
}
BeanPostProcessor — 后置处理器
**也是用于对组件的初始化操作, 但是优先级最高, 先于 initMethod/destroyMethod, InitializingBean等方式 **
它是一个接口, 当实现这个接口并将它添加到容器中后, 容器中所有的组件都会被它做一些初始化操作
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 在初始化之前调用
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization === " + beanName + " ===> " + bean);
return bean;
}
/**
* 在初始化之后调用
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization ~~~ " + beanName + " ===> " + bean);
return bean;
}
}
结果
@Value — 属性赋值
用于对 bean 的属性填充
相当于配置文件中的 property 标签
<bean id="person" class="com.lymboy.spring.annotation.bean.Person"
scope="singleton" init-method="" destroy-method="">
<property name="name" value="Sairo"></property>
<property name="age" value="18"></property>
</bean>
配置
@Configuration
public class MainConfigOfPropertyValues {
@Bean
public Person person() {
return new Person();
}
}
测试
public class IOCTest_PropertyValues {
private AnnotationConfigApplicationContext applicationContext;
@Test
public void test00() {
applicationContext = new AnnotationConfigApplicationContext(MainConfigOfPropertyValues.class);
printNames();
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
}
public void printNames() {
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
结果
可见, bean 的属性值为空, 可以用 @Value 给属性赋值
@Value 的参数
- 基本数值
- SpEL 表达式
- ${} 取出文件中的值
@PropertySource — 读取外部文件
@PropertySource 标注在配置类上, 参数是 String 数组, 其值是配置文件路径
然后需要在 bean 的属性上标注 @Value 属性
# person.properties
person.name=altraman
person.age=6
@Autowired, @Qualifier, @Primary — 自动装配
@Autowired
按类型装配, @Autowired会在容器中按照对应的类型去查找相关的对象实例, 如果在容器中找到多个类型匹配的实例, 则继续按属性名作为组件 id匹配
用法: 仅有一个参数 required, 意义为是否一定要匹配, 默认为 true, 可以标注在构造器, 方法, 属性, 参数上
对于以前的三层开发模型, Controller–>Service, Service–>Dao 程序员直接在声明一个下层组件的变量并标注 @Autowired 注解, 容器会自动创建相关组件的实例并注入其中.
@Controller
public class BookController {
@Autowired
private BookService bookService;
}
@Service
public class BookService {
@Autowired
private BookDao bookDao;
}
@Repository
public class BookDao {
}
测试
@Test
public void test01() {
applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
}
可见, 我们没有手动创建 BookDao 的实例, 容器就已经帮我们创建并注入到 BookService 中, 这就是控制反转和依赖注入!
如果在容器中找到多个类型匹配的实例, 则继续按属性名作为组件 id匹配
@Bean("bookDao2")
public BookDao bookDao() {
BookDao dao = new BookDao();
dao.setLable("2");
return dao;
}
@Test
public void test01() {
applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
BookService bookService = applicationContext.getBean(BookService.class);
// 查看依赖注入的 BookDao
System.out.println(bookService);
// 另一个 BookDao
BookDao dao = (BookDao) applicationContext.getBean("bookDao2");
System.out.println(dao);
applicationContext.close();
}
结果
@Qualifier
按 id 装配当存在多个 相同类型的组件时, 明确指定 id 去匹配
用法: 与 @Autowired 标注在相同的位置, 明确指定要匹配组件的 id
参数: 字符串, 指定的 id, 默认为空
@Service
public class BookService {
@Qualifier("bookDao2")
@Autowired()
private BookDao bookDao;
@Override
public String toString() {
return "BookService{" + "bookDao=" + bookDao + '}';
}
}
@Test
public void test02() {
applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
}
结果:
@Primary
设定首选的组件, 即在依赖注入式首先注入标记了此注解的组件, 与@Qualifier冲突
用法: 标注在配置类中
@Primary
@Bean("bookDao2")
public BookDao bookDao() {
BookDao dao = new BookDao();
dao.setLable("2");
return dao;
}
取消 @Qualifier 注解
@Resource
Java 本身自带的注解, 也是自动注入
用法: 与@Autowired相似, 参数 [name = id], 当使用name参数时, 其功能与 @Qualifier 相似, 也是按 id 匹配
Aware 注入
自定义组件要想使用容器底层的组件可以通过实现相关的 xxxAware 接口
@Profile — 环境切换
用于切换配置环境
可以标注在方法上也可以标注在配置类上
标注在方法/bean上
参数代表的环境的 id, 当参数为 default 时,默认此配置生效
package com.lymboy.spring.annotation.config;
@Configuration
@PropertySource("classpath:/db.properties")
public class MainConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private String driverClass;
/**
* 测试环境
*/
@Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://locaohost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
}
/**
* 开发环境
*/
@Profile("dev")
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://locahost:3306/dev");
dataSource.setDriverClass(driverClass);
return dataSource;
}
/**
* 生产环境
*/
@Profile("pro")
@Bean("proDataSource")
public DataSource dataSourcePro(@Value("${db.password}") String pwd) throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/pro");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
driverClass = resolver.resolveStringValue("${db.driver}");
}
}
环境切换
方法一: 命令行参数
选择测试环境
-Dspring.profiles.active=test
测试
package com.lymboy.spring.annotation;
public class IOCTest_Profile {
private AnnotationConfigApplicationContext applicationContext;
@Test
public void test00() {
applicationContext = new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
printNames();
applicationContext.close();
}
public void printNames() {
String[] names = applicationContext.getBeanNamesForType(DataSource.class);
for (String name : names) {
System.out.println(name);
}
}
}
方法二: 代码设置
@Test
public void test01() {
applicationContext = new AnnotationConfigApplicationContext();
applicationContext.getEnvironment().setActiveProfiles("dev", "test");
applicationContext.register(MainConfigOfProfile.class);
applicationContext.refresh();
printNames();
applicationContext.close();
}
标注在配置类上
当标注类上时, 只有当配置类生效时, 此类中的所有的配置才会生效
AOP 面向切面编程
动态代理 : 指在程序运行期间动态地将某段代码切入带指定位置进行运行的编程方式
相关概念
- Aspects: 切面, 通常是一个类, 即所谓的切面类,切面类中定义了切入点和通知
- JointPoint: 连接点, 执行的方法(需要通知的方法), 即下面的 ***div()***方法
- Advice: 通知, 即什么(类型)通知, eg: @Before, @After, @AfterReturning…
- Pointcut: 切入点, 对连接点进行拦截的定义, 即下面的 logStart(), logEnd() 等等
步骤
-
导入 aspects 模块
-
定义业务逻辑类, 定义一个实验方法
-
定义一个切面类
-
定义相关通知和切入点
通知方法
- 前置通知: 在目标方法(切点) 运行之前运行
- 后置通知: 在目标方法(切点) 运行之后运行
- 返回通知: 在目标方法(切点) 正常返回之后运行
- 异常通知: 在目标方法(切点) 运行出现异常之后运行
- 环绕通知: 动态代理, 目标方法运行前后运行
-
将切面和业务逻辑类交由容器管理
-
给切面类添加 @Aspect 注解, 指明其是切面类
-
在配置类中添加 @EnableAspectJAutoProxy 注解, 开启注解版的 AOP 功能
要点
- 通知(@Before(), @After()) 中填入的是切点表达式: @Before(“execution(public int com.lymboy.spring.annotation.aop.MathCalculator.*(…))”)
- 为了避免在通知中重复写相同的切点表达式可以使用 @PointCut 注解
假定设计一个业务类 MathCalculator
public class MathCalculator {
/**
* 除法操作
*/
public int div(int a, int b) {
return a/b;
}
}
定义一个日志切面类, 切面里的方法动态感知 MathCalculator.div() 运行到哪里了
@Aspect
public class LogAspects {
@Pointcut("execution(public int com.lymboy.spring.annotation.aop.MathCalculator.*(..))")
public void pointCut() {
}
@Before("execution(public int com.lymboy.spring.annotation.aop.MathCalculator.*(..))")
public void logStart() {
System.out.println("除法开始...参数列表是{}");
}
@After("com.lymboy.spring.annotation.aop.LogAspects.pointCut()")
public void logEnd() {
System.out.println("除法结束...");
}
@AfterReturning("com.lymboy.spring.annotation.aop.LogAspects.pointCut()")
public void logReturn() {
System.out.println("正常返回...运行结果{}");
}
@AfterThrowing("com.lymboy.spring.annotation.aop.LogAspects.pointCut()")
public void logException() {
System.out.println("出现异常...异常信息.{}");
}
}
测试
// 设计一个除 0 异常
@Test
public void test01() {
applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
MathCalculator math = (MathCalculator) applicationContext.getBean("com.lymboy.spring.annotation.aop.MathCalculator");
int div = math.div(10, 0);
System.out.println(div);
applicationContext.close();
}
可见, 在 div() 方法前后相关的通知方法运行了
事务处理
步骤
- 导入相关模块: 数据源、数据库连接、spring-jdbc模块
- 配置数据源、JdbcTemplate(Spring内置的简化操作的工具)
- 书写相关业务逻辑类
- 给插入方法添加事务注解(service方法)
- 添加事务管理器组件到容器中
- 开启事务管理器(@EnableXXX)
配置类
@Configuration
@EnableTransactionManagement // 必须开启事务管理器
@ComponentScan("com.lymboy.spring.annotation.tx")
public class TXConfig {
/**
* 数据源
*/
@Bean
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("123456");
dataSource.setJdbcUrl("jdbc:mysql://www.lymboy.com:3306/test");
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
// 事务管理器,实现PlatformTransactionManager接口
public PlatformTransactionManager platformTransactionManager() throws PropertyVetoException {
return new DataSourceTransactionManager(dataSource());
}
}
业务逻辑类
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insert() {
String sql = "INSERT INTO spring (`name`, `age`) VALUES(?, ?)";
String name = UUID.randomUUID().toString().substring(0, 5);
jdbcTemplate.update(sql, name, 19);
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
// 标示注解
@Transactional
public void insertUser() {
userDao.insert();
System.out.println("插入完成...");
int i = 10 / 0;
}
}