注解及其作用:
@Configuration :配置注解,修饰于配置类上,作用告诉Spring这是一个配置类,配置类等同于配置文件。
@ComponentScan:组件扫描注解,修饰于配置类上,指定容器启动时需要扫描的包,将包下面指定的注解标注的类粗册到容器中。
@Bean:标注在方法用来创建对象的时候,方法参数直接从容器中获取。参数可以不用标注@Autowired。
@Import
@Lazy:用于单例模式下
@PostConstruct:标记对象创建并赋值之后调用的方法(用于方法上)
@PreDestroy:标记容器移除对象之前调用的方法(用于方法上)
@Conditional(条件类.class):如果放在方法上面,按一定条件进行判断,满足条件给容器注册bean对象;如果放在类上面,表示满足这个条件,这个类中的所有bean注册才会生效,需要定一个判断条件类。示例如下:
首先创建一个条件类,该类需要实现org.springframework.context.annotation.Condition接口:
package jx.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class MacCondition implements Condition {
/**
* @param conditionContext 判断条件能使用的上下文(环境)
* @param annotatedTypeMetadata 注释信息
* @return 返回条件是否成立
*/
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
String property = environment.getProperty("os.name");
if (property.contains("Mac")){
return true;
}else {
return false;
}
}
}
调用示例:
package jx.config;
import jx.bean.Person;
import jx.condition.MacCondition;
import jx.condition.WindowsCondition;
import org.springframework.context.annotation.*;
@Configuration //告诉Spring这是一个配置类,配置类等同于配置文件。
@Conditional(WindowsCondition.class)//如果放在类上面,表示满足这个条件,这个类中的所有bean注册才会生效
public class MainConfig2 {
//如果放在方法上面,按一定条件进行判断,满足条件给容器注册bean对象
@Conditional(WindowsCondition.class)
@Bean("win")
public Person person1(){
return new Person("win", 100);
}
@Conditional(MacCondition.class)
@Bean("ios")
public Person person2(){
return new Person("ios",120);
}
}
给容器中注册组件:
1、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)
通过在配置类上面加组件扫描注解@ComponentScan(value="包名"),来指定容器启动时需要扫描的包,其中value指定要扫描的包名,此包下所有被@Controller/@Service/@Repository/@Component等注解标注的类都将被注册到容器中。
package jx.config;
import jx.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
//配置类等同于配置文件
@Configuration //告诉Spring这是一个配置类
@ComponentScan(value = "jx")//value:指定要扫描的包;
public class MainConfig {
//给容器注册一个bean;类型为返回值的类型,默认方法明作为id。
@Bean("person")//value属性用来记录id,默认是单例模式。
public Person person(){
return new Person("lisi",25);
}
}
组件扫描注解中有如下属性:
excludeFilters = Filter[] :扫描时按指定的规则排除哪些组件。
@ComponentScan(value = "jx", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})})
includeFilters = Filter[] :扫描时只需要包含哪些组件,必须同时使用useDefaultFilters=false;
@ComponentScan(value = "jx", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}, useDefaultFilters = false)
FilterType.ASSIGNABLE_TYPE:按照给定的类型进行过滤
FilterType.ASPECTJ:按照ASPECTJ表达式进行过滤
FilterType.REGEX:按照REGEX表达式进行过滤
FilterType.CUSTOM:按照用户定义的规则进行过滤,示例如下:
首先需要创建一个过滤类,指明过滤规则,该类需要实现TypeFilter接口:
package jx.config;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
//自定义一个过滤规则
public class MyTypeFilter implements TypeFilter {
@Override
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();
if (className.contains("er")){
return true;
}
//System.out.println("---->" + className);
return false;
}
}
然后在 @ComponentScan的属性中指明使用FilterType.CUSTOM,按照用户定义的规则进行过滤,具体代码如下:
@ComponentScan(value = "jx", includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})}, useDefaultFilters = false)
2、@Bean[导入的第三方包里面的组件]
@Bean("person")//value属性用来记录id,
public Person person(){
System.out.println("对象创建");
return new Person("lisi",25);
}
3、@Import[快速给容器中导入一个组件]
(1)@Import(要导入到容器中的组件):容器中就会自动注册这个组件,id默认是组件的全类名;
在配置类上面加上如下代码:Import中可以加入一个类,或者使用中扩号加入多个类的数组。
@Configuration //告诉Spring这是一个配置类
@Import({Color.class})//快速导入组件,默认id是组件的全类名
public class MainConfig2 {
(2)ImportSelector:返回需要导入的组件的全类名数组;(比较常用需要掌握)
定义一个ImportSelector类,需要实现ImportSelector接口:
package jx.condition;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"jx.bean.Blue"};//这里需要注意写的是类的全路径名
}
}
在Import中加入如下参数:
@Configuration //告诉Spring这是一个配置类
@Import({Color.class, MyImportSelector.class})//快速导入组件,默认id是组件的全类名
public class MainConfig2 {}
(3) ImprotBeanDefinitionRegistrar:手动注册bean到容器中。
(4) 使用Spring提供的FactoryBean(工厂bean)接口。(比较常用需要掌握)
定义一个工厂bean,这里需要实现FactoryBean接口:
package jx.bean;
import org.springframework.beans.factory.FactoryBean;
public class ColorFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
//配置该类是否是单例模式
@Override
public boolean isSingleton() {
return true;
}
}
在配置类中加入创建的工厂bean:
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
测试:需要注意这里输出的结果是工厂类中描述的类型而不是工厂类型!!!
注意:
(1)默认获取到的是工厂bean调用getObject创建的对象
(2)如果想要获取工厂bean本身,需要给id前面加一个&符号,如&colorFactoryBean
@Test
public void test4(){
Object bean = applicationContext.getBean("colorFactoryBean");
System.out.println(bean.getClass());//这里输出的结果是工厂类中描述的类型而不是工厂类型
}
生命周期相关的注解:
bean的生命周期:创建---初始化---销毁
容器管理bean的生命周期:可以自定义初始化和销毁方法。
对象创建:
单例:在容器启动的时候创建对象。
多例:在每次获取对象的时候创建对象。
对象初始化:在对象创建完成,并且赋值完成后,调用初始化方法。
对象销毁:
单例:容器关闭时销毁
多例:容器不会管理这个bean,也就是说在容器关闭后,容器不会调用销毁方法。
(1)通过在@Bean中设置initMethod = "类中定义的初始化方法名", destroyMethod = "类中定义的销毁方法名"
在类中定义初始化和销毁方法:
package jx.bean;
public class Car {
public Car(){
System.out.println("Car---创建");
}
public void init(){
System.out.println("Car---初始化");
}
public void destroy(){
System.out.println("Car---销毁");
}
}
然后在配置类中创建该类方法上的@Bean注解属性中进行指定:
@Bean(initMethod = "init", destroyMethod = "destroy")
public Car car(){
return new Car();
}
(2)通过让bean实现InitializingBean(定义初始化逻辑)和DisposableBean(定义销毁逻辑)接口。
package jx.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class Car implements InitializingBean, DisposableBean {
public Car(){
System.out.println("Car---创建");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Car---初始化");
}
@Override
public void destroy() throws Exception {
System.out.println("Car---销毁");
}
}
(3)使用JSR250中,@PostConstruct注解,在bean创建和赋值完成后执行初始化方法;@PreDestroy注解,在容器销毁bean之前通知进行清理工作。
package jx.bean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class Dog {
public Dog() {
System.out.println("Dog---Constructor---");
}
@PostConstruct//对象创建并赋值之后调用
public void init(){
System.out.println("Dog----init----");
}
@PreDestroy//容器移除对象之前调用
public void destroy(){
System.out.println("Dog----destroy---");
}
}
(4)BeanPostProcessor,bean的后置处理器:postProcessBeforeInitialization(Object bean, String beanName)---在初始化之前工作 ;postProcessAfterInitialization(Object bean, String beanName)方法在初始化之后工作。这个接口在Spring底层原理中应用很多。
package jx.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
//后置处理器,初始化前后进行处理工作
@Component//将后置处理器加入容器中
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization---"+ beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization---"+ beanName);
return bean;
}
}
属性赋值相关的注解:
@Value:
1、基本类型
2、SpEL类型:#{}
3、${}:取出配置文件(properties文件)中的值(环境变量中的值)
具体使用方法如下:
@Value("king")
private String name;
@Value("#{28-2}")
private Integer age;
@Value("${person.nikeName}")
private String nikeName;
配置类上要使用@PropertySource读取外部配置文件中的key/value保存到运行的环境变量中:
@PropertySource("classpath:/person.properties")
@Configuration
public class MainConfigOfPropertyValues {
@Bean
public Person person(){
return new Person();
}
}
自动装配:
(1) @Autowired---自动注入
1)默认优先按照类型去容器中找对应的组件:applicationContext.getBean(PersonDao.class)
2)如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找:applicationContext.getBean(“personDao”)
3)@Qualifier(“personDao”):使用@Qualifie指定需要装配的组件的id,而不是使用属性名
4)自动装配默认一定要将属性赋值好,没有就会报错,但是可以使用@Autowired(required=false),来指定该组件不是必须的。
5)@Primary,让Spring进行自动装配的时候,默认使用首选的bean,也可以继续使用@Qualifier指定需要装配bean的名字。
package jx.service;
import jx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class BookService {
@Qualifier("bookDao")
@Autowired(required = false)
public BookDao bookDao;
public void print(){
System.out.println(bookDao);
}
}
(2)Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[这两个都是java规范的注解]
@Resource:可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的,没有能支持@Primary功能,也没有支持@Autowired(required = false)的工能
@Inject:需要导入javax.inject的包,和@Autowired的功能一样
(3)@Autowired 可以标注在构造器、方法、参数以及属性上面,都是从容器中获取组件参数的值。
(a)标注在方法上:
(b)标注在构造器上:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略
(c)标注在参数上:
(4)自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx),自定义组件实现XXXAware接口,在创建对象的时候,会调用接口规定的方法注入相关组件;Aware把Spring底层一些组件导入自定义的Bean中。
@Profile:指定组件在哪个环境的情况下才能被注册到容器中,如果不指定,那么任何环境下都可以注册这个组件。
(1)加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境。
(2)写在配置类上,只有指定的环境的时候,整个配置类里面的所有配置才能开始生效。
(3)没有标注环境标识的bean在任何环境下都是加载的。
改变环境的两种方式:
(1)使用命令行动态参数:-Dspring.profiles.active=test(或者其他想要的测试环境)
(2)通过代码的方式激活某种环境:
//通过无参构造函数创建容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("test","dev");
//注册主配置类
applicationContext.register(MainConfigOfProfile.class);
//启动刷新容器
applicationContext.refresh();
AOP:动态代理,指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式。
1、在pom.xml中导入aop模块:Spring AOP (spring-aspents)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
2、定义一个业务逻辑类:在业务逻辑运行的时候将日志进行打印
package jx.aop;
public class MathCalculator {
public int div(int i, int j){
System.out.println("方法运行。。。");
return i/j;
}
}
3、定义一个日志切面类:切面类里面的方法需要动态感知业务方法运行到
通知方法:前置通知(@Before):在目标方法运行前之前运行
后置通知(@After):在目标方法运行结束之后运行
返回通知(@AfterReturning):在目标方法正常返回之后运行
异常通知(@AfterThrowing):在目标方法出现异常之后运行
环绕通知(@Around):动态代理,手动推进目标方法进行(joinPoint.procced())
4、给切面类的目标方法标注何时何地运行(通知注解);
package jx.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
@Aspect
public class LogAspects {
@Before("execution(public int jx.aop.MathCalculator.*(..))")
public void logStrat(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
System.out.println(joinPoint.getSignature().getName()+"方法运行,参数列表是:"+ Arrays.asList(args).toString()+"+}。");
}
@After("execution(public int jx.aop.MathCalculator.*(..))")
public void logEnd(){
System.out.println("方法运行结束。。。。。。");
}
@AfterReturning(value = "execution(public int jx.aop.MathCalculator.*(..))",returning = "result")
public void logReturn(Object result){
System.out.println("方法运行结束,返回结果是:"+result+"");
}
@AfterThrowing(value = "execution(public int jx.aop.MathCalculator.*(..))", throwing = "exception")
public void logException(Object exception){
System.out.println("方法出现异常。。。。"+exception.toString());
}
}
5、切面类和业务逻辑类都加入容器中
package jx.config;
import jx.aop.LogAspects;
import jx.aop.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
//业务逻辑类加入到容器中
@Bean
public MathCalculator mathCalculator(){
return new MathCalculator();
}
//切面类加入容器中
@Bean
public LogAspects logAspects(){
return new LogAspects();
}
}
6、告诉Spring哪个是切面类(@Aspect)
@Aspect
public class LogAspects {
7、给配置类中加@EnableAspectJAutoProxy(开启基于注解的aop模式)
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
总结创建aop的步骤:
(1)将业务逻辑组件和切面类都加入到容器中:告诉Spring哪个是切面类(@Aspect)
(2)在切面类上的每一个通知方式上标注通知注解,告诉Spring何时何地运行(切入点表达式)
(3)开启基于注解的aop模式:@EnableAspectJAutoProxy
AOP原理总结:
(1)@EnableAspectJAutoProxy开启AOP功能;
(2)@EnableAspectJAutoProxy会给容器中注册一个组件AnnotationAwareAspectJAutoProxyCreator;
(3)AnnotationAwareAspectJAutoProxyCreator是一个后置处理器;
(4)容器创建流程:
1)registerBeanPostProcessors()注册后置处理器,创建AnnotationAwareAspectJAutoProxyCreator对象;
2)finishBeanFactoryInitialization()初始化剩下的单实例bean
a、创建业务逻辑组件和切面组件
b、AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程
c、组件创建完成后,判断组件是否需要增强,如果是,切面的通知方法包装成增强器(Advisor);给业务逻辑组件创建一个代理对象。
(5)执行目标方法:
1)代理对象执行目标方法;
2)CglibAopProxy.intercept();
a、得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor)
b、利用拦截器的链式机制,依次进入每一个拦截器进行执行;
c、效果:
正常执行:前置通知-》目标方法-〉后置通知-》返回通知
出现异常:前置通知-》目标方法-〉后置通知-》异常通知
声明式事务:
环境搭建:
1、在pom.xml文件中导入相关依赖:数据源、数据库驱动、Spring-jdbc模块
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
2、在配置类中配置数据源、JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据,将其注册于容器中。
//数据源
@Bean
public DataSource dataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate() throws Exception{
//Spring对@Configuration类会进行特殊处理,给容器加组件的方法,多次调用都只是从容器中找组件
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
3、给方法上标注@Transactional ,表示当前方法是一个事务方法。
@Transactional
public void insertUser(){
userDao.insert();
System.out.println("插入完成!");
}
4、配置事务管理器来管理事务PlatformTransactionManager,将其注册在容器中。
//在容器中注册事务管理器
@Bean
public PlatformTransactionManager platformTransactionManager() throws Exception{
return new DataSourceTransactionManager(dataSource());
}
5、在配置类上标注@EnableTransactionManagement,开启基于注解的事物管理功能。
@EnableTransactionManagement//开启基于注解的事务管理功能
@PropertySource("classpath:/dbconfig.properties")
@ComponentScan
@Configuration
public class TxConfig {
声明式事务原理:
(1)@EnableTransactionManagement
利用TransactionManagementConfigurationSelector给容器中会导入AutoProxyRegistrar和ProxyTransactionManagementConfiguration
(2)AutoProxyRegistrar:给容器中注册一个InfrastructureAdvisotAutoProxyCreator组件,该组件利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行拦截
(3)ProxyTransactionManagementConfiguration:
1)给容器中注册事务增强器:
a)事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource解析事件
b)事务拦截器:TransactionInterceptor:保存了事务属性信息,事务管理器,他是一个MethodInterceptor,在目标方法执行的时候执行拦截器链。执行流程如下:1、先获取事务相关的属性;2、在获取PlatformTransactionManager,如果事先没有添加指定任何TransactionManager,最终会从容器中按照类型获取一个PlatformTransactionManager;3、执行目标方法,如果异常,获取到事务管理器,使用事务管理回滚操作;如果正常,利用食物管理器,提交事务。
扩展原理:
事件监听器
@EventListener(classes = {ApplicationEvent.class}):事件监听注解,classes属性标注监听哪些事件
@EventListener(classes = {ApplicationEvent.class})
public void listen(){
System.out.println("UserService====监听事件====");
}