Spring注解原理
Spring注解
原来Spring的使用
1.导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<optional>true</optional>
</dependency>
</dependencies>
2.配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
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-4.2.xsd">
<!-- 注册组件 -->
<bean id="person" class="com.atguigu.bean.Person">
<property name="age" value="18"></property>
<property name="name" value="liayun"></property>
</bean>
</beans>
3.实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
}
4.测试获取对象
public class MainTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
Object person = ac.getBean("person");
System.out.println(person);
}
}
注解的方式
现在我们不想再使用配置文件了,我们想用配置类代替配置文件
配置类
/**
* User:曹帅
* Date:2021/1/15
* Version:1.0
* 原来的配置文件现在被这个配置类所代替了
*/
@Configuration //此注解声明这是一个配置类
public class MainConfig {
//将这个返回的Person注入到Spring容器中,id默认时方法名
@Bean
public Person person(){
return new Person("lisi",20);
}
}
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
Object person = ac.getBean("person");
System.out.println(person);
使用注解排除一些类的加载
@Configuration //此注解声明这是一个配置类
@ComponentScan(value = "com.atguigu",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
})
public class MainConfig {
//将这个返回的Person注入到Spring容器中,id默认时方法名
@Bean
public Person person(){
return new Person("lisi",20);
}
}
Filter规则都有哪些
public enum FilterType {
ANNOTATION, //按照注解
ASSIGNABLE_TYPE, //自定义规则
ASPECTJ, //使用这个表达式
REGEX, //正则表达式
CUSTOM; //自定义规则
private FilterType() {
}
}
自定义规则比较常用
建立一个类去实现接口,
/**
* User:曹帅
* Date:2021/1/16
* Version:1.0
*/
public class MyTypeFilter implements TypeFilter {
/*
metadataReader:读取到的当前正在扫描的类的信息
MetadataReaderFactory:可以获取到其他任何类的信息
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
System.out.println("=====>"+className);
if (className.contains("er")) {
return true;
}
return false;
}
}
在配置类配置使用
@Configuration //此注解声明这是一个配置类
@ComponentScan(value = "com.atguigu",includeFilters = {
//@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}),
@ComponentScan.Filter(type = FilterType.CUSTOM,classes = MyTypeFilter.class)
})
public class MainConfig {
//将这个返回的Person注入到Spring容器中,id默认时方法名
@Bean
public Person person(){
return new Person("lisi",20);
}
}
注解填充进Spring容器中的对象默认是单实例的
//默认是单实例的,
Object bean = ac.getBean("person");
Object bean2 = ac.getBean("person");
System.out.println(bean==bean2);
实例的类型
prototype:
@Scope("prototype")
@Bean("person")
public Person person(){
return new Person("曹帅",22);
}
多例,容器启动时不会自动生成类,获取对象的时候对象被new出来,获取几次new几次
singleton:
单例默认情况下容器启动就会加载类,
懒加载,单例默认情况下不会再在容器创建的时候就创建对象了,第一次获取对象时才创建对象
@Lazy
//@Scope("prototype")
@Bean("person")
public Person person(){
System.out.println("person创建了");
return new Person("曹帅",22);
}
request
session
注册之前条件判断
在Bean被注入到容器中可以先进行判断,如果不符合条件就不加入进去
实现接口,重写方法,
public class LinuxCondition implements Condition {
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//1.获取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
//2.获取类加载器
ClassLoader classLoader = conditionContext.getClassLoader();
//3.获取当前环境信息
Environment environment = conditionContext.getEnvironment();
//4.获取到bean定义的注册类
BeanDefinitionRegistry registry = conditionContext.getRegistry();
//可以判断容器中bean的注册情况
boolean definition = registry.containsBeanDefinition("person");
String property = environment.getProperty("os.name");
if (property.contains("linux")) {
return true;
}
return false;
}
}
@Conditional({WindowsCondition.class})
@Bean("ym")
public Person person01(){
return new Person("杨梦",21);
}
@Conditional 也可以放在类上,代表着满足当前条件,这个类中配置的所有bean注册才能生效
@Import
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
可以直接导入外部的组件
@Configuration
@Import(LinuxCondition.class)
id默认是组件的全名,
() 里面可以是一个数组,可以一下子导入多个
另外还可以自定义选择器,
@Configuration
@Import({LinuxCondition.class, MyImportSelector.class})
public class MyImportSelector implements ImportSelector {
//返回值就是要导入到容器中的组件全类名
//annotationMetadata:当前标注 @Import注解的类的所有注解信息
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.atguigu.condition.WindowsCondition"};
}
}
自定义一个类实现接口,重写方法,容器会将方法返回的值对应的类加入到容器中
ImportBeanDefinitionRegistrar方式,直接手动注册
```java
public class MyImportBeanRegister implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 当前类的注解信息
* @param registry BeanDefinitionRegistry注册类
* 把所有需要添加到容器中的bean,
* BeanDefinitionRegistry有方法可以注册进去
*/
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean definition = registry.containsBeanDefinition("com.atguigu.condition.LinuxCondition");
boolean definition1 = registry.containsBeanDefinition("com.atguigu.condition.WindowsCondition");
if (definition && definition1) {
//指定bean定义信息:类型,作用域
BeanDefinition beanDefinition = new RootBeanDefinition(Sunny.class);
//注册一个bean指定bean名字
registry.registerBeanDefinition("sunny", beanDefinition);
}
}
}
FactoryBean,方式,在Spring和其他框架整合的时候经常会用到
```java
/**
* User:曹帅
* Date:2021/1/16
* Version:1.0
* 创建一个Spring定义的FactoryBean
*/
public class SunnyFactoryBean implements FactoryBean<Sunny> {
//返回一个Color对象,这个对象会添加到容器中
public Sunny getObject() throws Exception {
System.out.println("SunnyFactoryBean------getObject");
return new Sunny();
}
public Class<?> getObjectType() {
return Sunny.class;
}
//是否是单例
public boolean isSingleton() {
return true;
}
}
@Test
public void testImport() {
printBeans(ac);
Object bean = ac.getBean("sunnyFactoryBean");
//将这个工厂bean注册到容器中,虽然看似是这个类型的,
//但是实际获取到的却是Sunny类型的
System.out.println("bean类型"+bean.getClass());
}
默认获取到的是工厂bean创建的对象,getObject() 返回的对象,
要获取工厂bean本身,
@Test
public void testImport() {
printBeans(ac);
Object bean = ac.getBean("&sunnyFactoryBean");
//将这个工厂bean注册到容器中,虽然看似是这个类型的,
//但是实际获取到的却是Sunny类型的
System.out.println("bean类型"+bean.getClass());
}
容器中对象的生命周期
1.通过@Bean注解指定
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----destroy");
}
}
/**
* User:曹帅
* Date:2021/1/16
* Version:1.0
* bean的生命周期
* 创建---初始化---销毁的过程
* 容器管理bean的生命周期
* 我们可以自定义初始化和销毁方法:
* 容器在bean进行到当前生命周期的时候,调用这些方法
* 1.指定初始化销毁方法
*/
@Configuration
public class MainConfigOfLifeCycle {
@Bean(initMethod = "init",destroyMethod = "destroy")
public Car car() {
return new Car();
}
}
@Test
public void testImport() {
printBeans(ac);
ac.close();
}
初始化:对象创建完成,并赋值好,调用初始化方法
销毁:容器关闭,但是多例情况下,容器调用销毁方法
其原理就是让Bean实现一些接口来实现的,
InitializingBean DisposableBean
那么既然实际上是继承了两个接口达到了这个目的,我们直接继承两个接口,写方法也可以的
@Component
public class Cat implements InitializingBean, DisposableBean {
public Cat(){
System.out.println("cat constructor");
}
public void destroy() throws Exception {
System.out.println("aaaa, a cat has been destroyed");
}
public void afterPropertiesSet() throws Exception {
System.out.println("hhh,a cat become");
}
}
@ComponentScan("com.atguigu.bean")
@Configuration
public class MainConfigOfLifeCycle {
@Scope("prototype")
@Bean(initMethod = "init",destroyMethod = "destroy")
public Car car() {
return new Car();
}
}
@Test
public void testImport() {
//printBeans(ac);
ac.getBean("cat");
ac.close();
}
可以使用JSR250里面的规范
@PostConstruct,新生时
@PreDestroy,销毁时
@Component
public class Dog {
public Dog(){
System.out.println(" a dog constructor");
}
//对象创建并赋值之后调用
@PostConstruct
public void init(){
System.out.println("dog init");
}
//容器移除对象之前
@PreDestroy
public void destroy(){
System.out.println("dog ---preDestroy");
}
}
BeanPostProcessor bean的后置处理器,在bean初始化前后进行一些处理工作
postProcessBeforeInitialization:在初始化之前工作
postProcessAfterInitialization:在构造方法执行之后
属性赋值
非常传统的直接赋值
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
/*
1.基本数值
2.SPEL #{}
3.${} 取出配置文件中的值(在运行环境变量中的值)
*/
@Value("${person.nickName}")
private String name;
@Value("#{20-2}")
private Integer age;
}
在配置类上声明扫描哪些配置文件,spring会把这些配置文件的值放入环境中去
@PropertySource(value = {"classpath:application.properties"})
@Configuration
public class MainConfigOfPropertyValue {
@Bean
public Person person(){
return new Person();
}
}
@Test
public void testImport() {
printBeans(ac);
Object person = ac.getBean("person");
System.out.println(person);
ConfigurableEnvironment environment = ac.getEnvironment();
String property = environment.getProperty("person.nickName");
System.out.println(property);
ac.close();
}
person.nickName=杨小梦
自动注入
@Autowired会自动进行注入,
@Test
public void test01() {
BookService bookService = ac.getBean(BookService.class);
bookService.print();
BookDao bookDao = ac.getBean(BookDao.class);
System.out.println(bookDao); //两个对象的hash值相等
}
有时候也会有冲突,
@Test
public void test01() {
BookService bookService = ac.getBean(BookService.class);
//这个自动装配的是label 1, 默认按照类型去容器里面找,
//2.如果找到相同类型的多个,就按照属性名作为Id去容器里面找
bookService.print();
BookDao bookDao = ac.getBean(BookDao.class);
System.out.println(bookDao.getLabel());
}
当然最有效的是不让他们产生冲突,直接指定好名字就行了
@Service
public class BookService {
@Qualifier("bookDao")
@Autowired
private BookDao bookDao;
public void print() {
System.out.println(bookDao.getLabel());
}
}
@Primary可以声明这个bean比较优先装配
也可以使用@Resource这个注解注入到Spring容器中
@Resource(JSR250)
@Inject(JSR330)需要导入 javax.inject的包,和Autowired功能一样
@Autowired: 构造器,参数,方法,属性
@Component
public class Boss {
private Car car;
public Car getCar() {
return car;
}
//标注在方法上,Spring创建当前对象就会调用方法完成赋值
//方法使用的参数,自定义类型的值从ioc容器中获取
@Autowired
public void setCar(Car car) {
this.car = car;
}
}
构造器所用的组件,也都是从容器获取
@Component
public class Boss {
private Car car;
@Autowired
public Boss(Car car) {
this.car = car;
System.out.println("Boss's constructor with parameter");
}
}
public Boss(@Autowired Car car) {
this.car = car;
System.out.println("Boss's constructor with parameter");
}
甚至如果只有一个有参构造器,还可以省略
public Boss(Car car) {
this.car = car;
System.out.println("Boss's constructor with parameter");
}
@Bean标注的方法创建对象的时候,方法参数的值从容器中获取,不写@Autowired也是可以的
自定义组件想要使用Spring容器底层的一些组件(ApplicationCOntext,Beanfactory,);
那么自定义组件实现xxxAware ,这个组件就可以获取到类中的内容
@Component
public class Red implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("传入的ioc"+applicationContext);
}
}
这些xxxAware,是使用xxxProcessor来进行处理的
Profile
/**
* User:曹帅
* Date:2021/1/17
* Version:1.0
* Profile:
* Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能
*
* 开发环境,测试环境,生产环境
* 数据源:A/B/C
* @Profile可以指定环境使用
* 加入了环境标识的bean只有这个环境被激活的时候才能注册到容器中,
* 环境默认是 default
*/
@PropertySource("classpath:application.properties")
@Configuration
public class MainConfigOfProfile {
@Value("${db.user}")
private String user;
@Value("${db.password}")
private String password;
@Value("${db.driverClass}")
private String driverClass;
@Profile(value = "test")
@Bean("testDataSource")
public DataSource dataSourceTest(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setUrl("jdbc:mysql://localhost:3306/ssm");
dataSource.setDriverClassName(driverClass);
return dataSource;
}
@Profile(value = "dev")
@Bean("devDataSource")
public DataSource dataSourceDev(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setUrl("jdbc:mysql://localhost:3306/ssm");
dataSource.setDriverClassName(driverClass);
return dataSource;
}
@Profile(value = "prod")
@Bean("prodDataSource")
public DataSource dataSourceProd(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setUrl("jdbc:mysql://localhost:3306/ssm");
dataSource.setDriverClassName(driverClass);
return dataSource;
}
}
@Test
public void test01() {
String[] names = ac.getBeanNamesForType(DataSource.class);
for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}
}
修改环境:
1.在运行test方法的时候设置虚拟机环境参数,-Dspring.profile.active=test
2.上下文对象中获取环境,set参数,可以设置一个数组
@Test
public void test01() {
ac.getEnvironment().setActiveProfiles("test","dev");
ac.register(MainConfigOfProfile.class);
ac.refresh();
String[] names = ac.getBeanNamesForType(DataSource.class);
for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}
}
3.在配置类的头上写Profile注解,代表着只有在那样一个环境下,才会生效
AOP
导包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
aop注解需要配置很多东西,
首先应该在配置类上面把它开启
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
@Bean
public MathCalculator mathCalculator(){
return new MathCalculator();
}
@Bean
public LogAspects logAspects(){
return new LogAspects();
}
}
最起码有一个目标类,这个目标类就很普通了
public class MathCalculator {
public int div(int i, int j) {
return i / j;
}
}
还要有一个专门的aop的类
/**
* User:曹帅
* Date:2021/1/17
* Version:1.0
* this is a log Aspects class:
* 2.the methods in this class should to be aware the procedure for a reality class
* 3.aware method:
* before information(@Before): logStart,
* after information(@After): logEnd it always has been execute after target method has been executed ever normal or has exception
* return information(@AfterReturning): logReturn
* err information(@AfterThrowing): logException
* around information(@Around): dynamic proxy, push target method active by ourselves(joinPoint.proceed )
* 4.target when and where active for methods in aspect class
* 5.put aspect class and target class into IOC
* 6.information spring which class is the aspect class
* 7.open enable AspectProxy
*/
@Aspect
public class LogAspects {
// common expression
@Pointcut("execution(public int com.atguigu.aop.MathCalculator.*(..))")
public void pointCut(){
}
//@Before before method expression
@Before("pointCut()")
public void logStart(){
System.out.println("method start...");
}
@After("pointCut()")
public void logEnd(){
System.out.println("method end ...");
}
@AfterReturning("pointCut()")
public void logReturn(){
System.out.println("method return ...");
}
@AfterThrowing("pointCut()")
public void logException(){
System.out.println("method exception ...");
}
}
这个类里面声明了一个方法,可以自动的找到要切入哪一个类的那个方法,方法名字,返回参数什么的都可以设定,也可以使用正则表达式类型的,
将这两个类都要放在容器里面,要不然是不会自动执行的
测试的时候,在目标类的方法执行的时候切面类就会发觉
也可以在切面中获取目标方法中的内容,都在JoinPoint里面,这个参数可传可不传
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("method:"+joinPoint.getSignature()+" start... ,the args are"+ Arrays.asList(args));
}
return 中可以获取到返回的内容
@AfterReturning(value = "pointCut()",returning = "result")
public void logReturn(Object result) {
System.out.println("method return ..."+result);
}
throwing异常情况下
@AfterThrowing(value = "pointCut()",throwing = "e")
public void logException(Exception e) {
System.out.println("method exception ...");
}
如果想要使用到JoinPoint里面的内容,那么JoinPoint一定要放到参数中的第一位
AOP的原理
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
}
调用@Import 给容器中导入这个类
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
}
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
利用AspectJAutoProxyRegistrar给容器中注入自定义的类 AnnotationAwareAspectJAutoProxyCreator
如果一个类需要使用AOP,我们配置成功了,那么我们实际上放入IOC容器中的是我们自己生成的一个代理对象,(增强后的对象)
这个对象中有增强器,目标对象
事务
1.导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
2.配置数据远
@PropertySource("classpath:application.properties")
@EnableTransactionManagement
@ComponentScan("com.atguigu.tx")
@Configuration
public class TxConfig {
@Value("${db.user}")
private String user;
@Value("${db.password}")
private String password;
@Value("${db.driverClass}")
private String driverClass;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setUrl("jdbc:mysql://localhost:3306/ssm");
dataSource.setDriverClassName(driverClass);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(){
//Sring对Configuratiojn文件会特殊处理,不会再次调用一遍方法的
//给容器中加组件的方法,多次调用都只是从容器中找组件而已
JdbcTemplate template = new JdbcTemplate(dataSource());
return template;
}
//注册事务管理器在服务器中
@Bean
public PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(dataSource());
}
}
事务拦截器:在目标方法执行的时候,执行拦截器链,
事务拦截器:先获取事务相关的属性,再来获取 PlateformTransactionManager
其余
事件监听
@Test
public void test01(){
//发布事件
ac.publishEvent(new ApplicationEvent(new String("我发布的事件")){});
ac.close();
}
/**
* User:曹帅
* Date:2021/1/17
* Version:1.0
* 自己开发一个事件
* 步骤:
* 写一个监听器来监听某个事件 ApplicationEvent极其子类
* 把监听器加入到容器,
* 只要容器中有相关事件的发布,我们就能监听到这个事件
* ContextRefreshedEvent:容器刷新完成所有bean都完全创建,会发布
* ContextClosedEvent:关闭容器也会发布事件
* 我们自己也可以发布一个事件:
*
*/
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
//当容器中发布此事件后,方法触发
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("收到事件" + event);
}
}