@springBootApplication
public class DemoApplication(){
public static void main(String[] args){
SpringApplication.run(DemoApplication.class,args);
}
}
标签名 | 含义 |
---|---|
@Service |
用于标注业务层组件,service层或者manager层 |
@Controller |
用于标注控制层组件,action层 |
@Repository |
用于标注数据访问组件,即DAO组件 |
@Component |
用于泛指组件,不好归类时,我们选择这种组件 |
public class NamePointcut {
/**
* 切点被命名为 method1,且该切点只能在本类中使用
*/
@Pointcut("within(net.deniro.spring4.aspectj.*)")
private void method1() {
}
/**
* 切点被命名为 method2,且该切点可以在本类或子孙类中使用
*/
@Pointcut("within(net.deniro.spring4.aspectj.*)")
protected void method2() {
}
/**
* 切点被命名为 method3,且该切点可以在任何类中使用
* 这里还使用了复合运算
*/
@Pointcut("method1() && method2()")
public void method3() {
}
}
切点
@Target(ElementType.TYPE) //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
processServletContainerInitializers()
时记录下注解的类名,然后在Step 4和Step 5中都来到processAnnotationsStream
这个方法,使用BCEL的ClassParser
在字节码层面读取了/WEB-INF/classes
和某些jar(应该可以在叫做fragments的概念中指定)中class文件的超类名和实现的接口名,判断是否与记录的注解类名相同,若相同再通过org.apache.catalina.util.Introspection
类load为Class对象,最后保存起来,于Step 11中交给org.apache.catalina.core.StandardContext
,也就是tomcat实际调用ServletContainerInitializer.onStartup()
的地方。Java SPI、servlet3.0与@HandlesTypes源码分析
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AnnatDemo{
public int value();
}
//比如@AnnatDemo作用于以下方法:
public interface IClientProtocolEx extends IProtocol {
int METHOD_START=0;
@AnnatDemo(METHOD_START)
public String say(String person);
}
//使用如下代码进行反射
Class ipt=IClientProtocalEx.class;
Method[] mts=ipt.getMethod();
for(Method mt:mts){
AnnatDemo ad=mt.getAnnotation(AnnatDemo.class);//如果方法上 没有该注解 则返回null
int value=ad.value();
System.out.println("value:"+value);
}
private final Logger logger = LoggerFactory.getLogger(当前类名.class)
则可以使用注解@Slf4jlog.info(str)
来写入日志@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
require = ture 时,表示解析被标记的字段或方法,一定有对应的bean存在。
require = false 时,表示解析被标记的字段或方法,没有对应的bean存在不会报错。
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* Defaults to {@code true}.
*/
boolean required() default true;
}
AutowiredAnnotationBeanPostProcessor
AbstractApplicationContext#refresh()
方法时会执行obtainFreshBeanFactory()
方法,而这个方法执行时,会在DefaultListableBeanFactory#beanDefinitionNames
数组中添加internalAutowiredAnnotationProcessor
。而internalAutowiredAnnotationProcessor
是和AutowiredAnnotationBeanPostProcessor
被一起注册到registerPostProcessor
中if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
registerBeanPostProcessors(beanFactory)
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
// 这个方法我们使用委派模式进行委派
public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
...
registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
...
}
getBeanNamesForType
是对前面加载的internalAutowiredAnnotationProcessor
进行转换成AutowiredAnnotationBeanPostProcessor
然后把返回值postProcessorNames
转为priorityOrderedPostProcessors
然后注册到registerBeanPostProcessors
中private static void registerBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanPostProcessor> postProcessors) {
for (BeanPostProcessor postProcessor : postProcessors) {
beanFactory.addBeanPostProcessor(postProcessor);
}
}
public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
Assert.notNull(beanPostProcessor, "BeanPostProcessor must not be null");
// Remove from old position, if any
this.beanPostProcessors.remove(beanPostProcessor);
// Track whether it is instantiation/destruction aware
if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {
this.hasInstantiationAwareBeanPostProcessors = true;
}
if (beanPostProcessor instanceof DestructionAwareBeanPostProcessor) {
this.hasDestructionAwareBeanPostProcessors = true;
}
// Add to end of list
this.beanPostProcessors.add(beanPostProcessor);
}
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
...
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
...
getBean(beanName);
...
}
}
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
return createBean(beanName, mbd, args);
}
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
...
doCreateBean(beanName, mbdToUse, args);
...
}
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
...
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
...
}
//
protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof MergedBeanDefinitionPostProcessor) {
MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
}
}
}
getBeanPostProcessors
方法获取的就是上文中添加的AutowiredAnnotationBeanPostProcessor
的集合beanPostProcessors
。AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
。public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
...
metadata = buildAutowiringMetadata(clazz);
...
}
//有点小问题
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
return new InjectionMetadata(clazz, elements);
}
findAutowiredAnnotation(bridgedMethod)
找到这个类的autowire注解的类,添加到InjectionMetadata对象中。然后在checkConfigMembers方法中又注册到beanDefinition中。@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
public void checkConfigMembers(RootBeanDefinition beanDefinition) {
Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size());
for (InjectedElement element : this.injectedElements) {
Member member = element.getMember();
if (!beanDefinition.isExternallyManagedConfigMember(member)) {
beanDefinition.registerExternallyManagedConfigMember(member);
checkedElements.add(element);
if (logger.isTraceEnabled()) {
logger.trace("Registered injected element on class [" + this.targetClass.getName() + "]: " + element);
}
}
}
this.checkedElements = checkedElements;
}
private TbPositionDAO positionMapper;
@Autowired
public PositionServiceImpl(TbPositionDAO tbPositionDAO) {
this.positionMapper = tbPositionDAO;
}
//Component和Bean可以通用的
public class WireThirdLibClass {
@Bean
public ThirdLibClass getThirdLibClass() {
return new ThirdLibClass();
}
}
//只能使用Bean
@Bean
public OneService getService(status) {
case (status) {
when 1:
return new serviceImpl1();
when 2:
return new serviceImpl2();
when 3:
return new serviceImpl3();
}
}
@SpringBootApplication
@EnableScheduling //开启定时任务
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
//表示上一个方法结束后5秒
@Scheduled(fixedDelay = 5000)
//表示每隔3秒
@Scheduled(fixedRate = 3000)
//表示每天8时30分0秒执行
@Scheduled(cron = "0 0,30 0,8 ? * ? ")
* 第一位,表示秒,取值0-59
* 第二位,表示分,取值0-59
* 第三位,表示小时,取值0-23
* 第四位,日期天/日,取值1-31
* 第五位,日期月份,取值1-12
* 第六位,星期,取值1-7,星期一,星期二...,注:不是第1周,第二周的意思
另外:1表示星期天,2表示星期一。
* 第7为,年份,可以留空,取值1970-2099
(*)星号:可以理解为每的意思,每秒,每分,每天,每月,每年...
(?)问号:问号只能出现在日期和星期这两个位置。
(-)减号:表达一个范围,如在小时字段中使用"10-12",则表示从10到12点,即10,11,12
(,)逗号:表达一个列表值,如在星期字段中使用"1,2,4",则表示星期一,星期二,星期四
(/)斜杠:如:x/y,x是开始值,y是步长,比如在第一位(秒) 0/15就是,从0秒开始,每15秒,最后就是0,15,30,45,60 另:*/y,等同于0/y
0 0 3 * * ? 每天3点执行
0 5 3 * * ? 每天3点5分执行
0 5 3 ? * * 每天3点5分执行,与上面作用相同
0 5/10 3 * * ? 每天3点的 5分,15分,25分,35分,45分,55分这几个时间点执行
0 10 3 ? * 1 每周星期天,3点10分 执行,注:1表示星期天
0 10 3 ? * 1#3 每个月的第三个星期,星期天 执行,#号只能出现在星期的位置
@Component
@EnableScheduling
@Transactional
public class ReleasedTask implements SchedulingConfigurer{
private Logger log = Logger.getLogger(getClass());
private TradeConfigMapper tradeConfigMapper;
@Autowired
private TradeConfigService configService;
@Autowired
private UserAccountService accountService;
@Autowired
private UserAccountDetailService accountDetailService;
private String cron;
@Autowired
public ReleasedTask(TradeConfigMapper tradeConfigMapper) {
// 获取每几天释放一次参数(单位:天)
this.tradeConfigMapper = tradeConfigMapper;
String interval = tradeConfigMapper.getTimeInterval().getConfigValue();
this.cron = "0 0 3 */" + interval + " * ?";
//cron = "*/10 * * * * ?";
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(new Runnable() {
@Override
public void run() {
//是否执行自动释放
String autoRelease = configService.getConfig(TradeConstants.TRADE_CONFIG_AUTO_RELEASE);
if (autoRelease.equals("1")) {
// 任务逻辑
log.info("任务执行---------");
log.info("任务执行完---------");
}
}
}, new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
// 任务触发,可修改任务的执行周期
CronTrigger trigger = new CronTrigger(cron);
Date nextExec = trigger.nextExecutionTime(triggerContext);
return nextExec;
}
});
}
}
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)
@Service(value ="employeeService")
public class EmployeeService
属性名 | 说明 |
---|---|
name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
propagation | 事务的传播行为,默认值为 REQUIRED。 |
isolation | 事务的隔离度,默认值采用 DEFAULT。 |
timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
//@EnableAutoConfiguration的复合注解情况
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
@Conditional
这个注解表示只有在所有指定条件匹配时, 组件才有资格进行注册。@Conditional
可以通过以下任何方式使用:
@Component
和@Configuration
的类上作为一个类型注解使用@Bean
方法的方法级注解@Conditional
标记@Configuration
类, 则与该类关联的所有@Bean
方法, @Import
注解和@ComponentScan
注解都将受条件限制。@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
//profileCondtion.class
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata 这个玩意) {
// metadata 这个玩意是你以注解方式配置的Spring的、尚未被IOC容器处理的内容 (又分AnnotationMetadata和MethodMetadata 说多了)
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
@Profile
注解配置的方法/类, 然后解析其中的value
值形成一个MultiValueMap
结构。acceptsProfiles
的验证, 则@Conditional(ProfileCondition.class)
成立applicationContext.getEnvironment().setActiveProfiles("chinese")
;设置配置, 也可以通过注解@ActiveProfiles(..)
设置。@Configuration
public class AppConfig {
@Profile("english")
@Bean
public English getEnglish() { return new English(); }
@Profile("chinese")
@Bean
public Chinese getChinese() { return new Chinese(); }
}
class Chinese { }
class English { }
// 测试类
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.getEnvironment().setActiveProfiles("chinese");
applicationContext.register(AppConfig.class);
applicationContext.refresh();
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName); // 这里你可以看看打印的bean是否和你想的一样
}
}
}
//TODO
@Configuration
public class ProfileDatabaseConfig {
@Bean("dataSource")
@Profile("development")
public DataSource embeddedDatabase() { ... }
@Bean("dataSource")
@Profile("production")
public DataSource productionDatabase() { ... }
}
环境|名称|数据源
开发环境|develop|/dev
测试环境|test|/test
生产环境|master|/master
application.properties
或application.yml
文件中,通过@ConfigurationProperties
注解,我们可以方便的获取这些参数值myapp:
mail:
enabled: true
default-subject: "This is a Test"
@Value
注解来访问这些属性,但是这个相对比较笨重。@ConfigurationProperties
注解@Data
@Component
@ConfigurationProperties(prefix = "myapp.mail")
public class mailModuleProperties{
private Boolean enabled = Boolean.TRUE;
private String defaultSubject;
}
@Component
注解进行Bean注册//代码形式注册
@Configuration
class PropertiesConfig{
@Bean
public MailModuleProperties mailModuleProperties(){
return new MailModuleProperties();
}
}
//注解形式注册,不建议
@Configuration
@EnableConfigurationProperties(MailModuleProperties.class)
class PropertiesConfig{
}
ignoreInvalidFields
字段,这时候如果没有的话,则会初始化对应属性为NULL@Data
@Component
@ConfigurationProperties(prefix = "myapp.mail", ignoreInvalidFields = true)
public class mailModuleProperties{
private Boolean enabled = Boolean.TRUE;
private String defaultSubject;
}
myapp:
mail:
enabled: true
default: "This is a Test"
unkown-property: "foo"
Spring Boot默认会忽略没有绑定的属性,也不会运行报错,如果想要报错则使用
ignoreUnknownFields
字段即可
@Data
@Component
@ConfigurationProperties(prefix = "myapp.mail", ignoreUnknownFields = false)
public class mailModuleProperties{
private Boolean enabled = Boolean.TRUE;
private String defaultSubject;
}
如上操作就会正常报错
@Data
@Validated
@Component
@ConfigurationProperties(prefix = "myapp.mail")
public class mailModuleProperties{
@NotNull
private Boolean enabled = Boolean.TRUE;
@NotEmpty
private String defaultSubject;
}
在启动的时候,则会按照标签进行检查,如果不符合则会报
BindValidationException
异常
@Data
@Component
@ConfigurationProperties(prefix = "myapp.mail")
public class mailModuleProperties{
private List<String> smtpServers;
}
myapp.mail.smtpServers[0]=server1
myapp.mail.smtpServers[1]=server2
myapp:
mail:
smtp-servers:
- server1
- server2
@Data
@Component
@ConfigurationProperties(prefix = "myapp.mail")
public class mailModuleProperties{
// 这个注释是用来声明单位的
@DurationUnit(ChronoUnit.SECONDS)
private Duration pauseBetweenMails;
}
默认单位毫秒
myapp.mail.pause-between-mails=5s
myapp:
mail:
pause-between-mails: 5s
单位 | 英文 | 中文 |
---|---|---|
ns | nanoseconds | 纳秒 |
us | microseconds | 微秒 |
ms | milliseconds | 毫秒 |
s | seconds | 秒 |
m | minutes | 分 |
h | hours | 时 |
d | days | 天 |
@Data
@Component
@ConfigurationProperties(prefix = "myapp.mail")
public class mailModuleProperties{
// 这个注释是用来声明单位的
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize maxAttachmentSize = DataSize.ofMegabytes(2);
}
默认单位毫秒
myapp.mail.max-attachment-size=1MB
myapp:
mail:
max-attachment-size: 1MB
单位 | 英文 |
---|---|
B | bytes |
KB | kilobytes |
MB | megabytes |
GB | gigabytes |
TB | terabytes |
myapp:
mail:
max-attachment-weight: 5kg
@Data
@Component
@ConfigurationProperties(prefix = "myapp.mail")
public class mailModuleProperties{
private Weight maxAttachmentWeight;
}
class WeightConverter implements Converter<String, Weight> {
@Override
public Weight convert(String source){
//从字符串转换成Weight对象
}
}
@Configuration
class PropertiesConfig{
@Bean
@ConfigurationPropertiesBinding //注册该转换器为数据绑定
public WeightConverter weightConverter(){
return new WeightConverter();
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
// 注入普通字符串
@Value("normal")
private String normal;
// 注入操作系统属性
@Value("#{systemProperties['os.name']}")
private String systemPropertiesName;
//注入表达式结果
@Value("#{ T(java.lang.Math).random() * 100.0 }")
private double randomNumber;
// 注入其他Bean属性:注入beanInject对象的属性another,类具体定义见下面
@Value("#{beanInject.another}")
private String fromAnotherBean;
//beanInject类
@Component
public class BeanInject {
@Value("其他Bean的属性")
private String another;
public String getAnother() {
return another;
}
public void setAnother(String another) {
this.another = another;
}
}
// 注入文件资源
@Value("classpath:com/hry/spring/configinject/config.txt")
private Resource resourceFile;
// 注入URL资源
@Value("http://www.baidu.com")
private Resource testUrl;
application.properties
或application.yml
文件@PropertySource
加载
config.properties
,如下book.name=bookName
anotherfile.configinject=placeholder
config_placeholder.properties
,如下book.name.placeholder=bookNamePlaceholder
@Component
// 引入外部配置文件组:${app.configinject}的值来自config.properties。
// 如果相同,这里使用了配置文件的属性
@PropertySource({"classpath:com/hry/spring/configinject/config.properties",
"classpath:com/hry/spring/configinject/config_${anotherfile.configinject}.properties"})
public class ConfigurationFileInject{
@Value("${app.name}")
private String appName; // 这里的值来自application.properties,spring boot启动时默认加载此文件
@Value("${book.name}")
private String bookName; // 注入第一个配置外部文件属性
@Value("${book.name.placeholder}")
private String bookNamePlaceholder; // 注入第二个配置外部文件属性
@Autowired
private Environment env; // 注入环境变量对象,存储注入的属性值
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("bookName=").append(bookName).append("\r\n")
.append("bookNamePlaceholder=").append(bookNamePlaceholder).append("\r\n")
.append("appName=").append(appName).append("\r\n")
.append("env=").append(env).append("\r\n")
// 从eniroment中获取属性值
.append("env=").append(env.getProperty("book.name.placeholder")).append("\r\n");
return sb.toString();
}
}