Как первоклассный проект Java с открытым исходным кодом, я считаю, что все использовали Spring, но, возможно, все используют только наиболее часто используемые функции Spring. Spring действительно огромен, и многие функции могут не использоваться в течение всей жизни. Сегодня я перечислю вы весной.Вещи, которые вы могли не знать. Во-первых, это может помочь вам прочитать исходный код в будущем и понять, почему Spring так написал, Во-вторых, его можно использовать как резерв знаний, Когда другие этого не знают, вы просто знаете этот момент, и вы можете сделать это, разделив три и пять на два, хе-хе. В-третьих, вы можете иметь больше капитала, когда хвастаетесь. . . Конечно, самое главное — иметь более полное представление о Spring.
register
Теперь официальная рекомендация должна состоять в том, чтобы использовать стиль JavaConfig для завершения настройки Spring, что также является текущим основным использованием. Мы часто пишем это:
@Configuration
@ComponentScan
public class AppConfig {
}
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
Этот код слишком прост для объяснения, но мы можем разбить его на части:
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
Класс конфигурации регистрируется во второй строке кода.
Эффект тот же, помимо регистрации класса конфигурации, мы также можем зарегистрировать bean-компонент отдельно:
@Component
public class Service {
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Service.class);
context.refresh();//很重要
System.out.println(context.getBean(Service.class).getClass().getSimpleName());
}
}
Таким образом, мы можем завершить инъекцию бина.Здесь есть очень важная деталь.Метод обновления нужно вызвать, иначе будет сообщено об ошибке:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Service.class);
System.out.println(context.getBean(Service.class).getClass().getSimpleName());
}
}
registerBean
Хотя приведенный выше метод может зарегистрировать только компонент, в классе компонента вы должны отметить @Component, @Service или @Repository. Если вы не хотите использовать область по умолчанию, вы также должны отметить @Scope. Есть ли способ? Могу ли я не использовать различные аннотации в классе компонентов? В этот момент в игру вступает registerBean:
public class Service {
public Service(String str){
System.out.println(str);
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean("myService", Service.class, () -> new Service("Hello"), z -> {
z.setScope("prototype");
});
context.refresh();
System.out.println(context.getBean("myService").getClass().getSimpleName());
System.out.println(context.getBeanDefinition("myService").getScope());
}
}
Я зарегистрировал bean-компонент с именем myService, класс — Service, а область — прототип, и вызывается конструктор с параметрами:
BeanPostProcessor
Если два вышеперечисленных пункта не важны, то этот — тяжеловес. BeanPostProcessor — одна из точек расширения Spring, а BeanPostProcessor — интерфейс. . Внутри Spring BeanPostProcessor также широко используется для выполнения различных функций. Мы можем видеть, сколько классов в Spring реализуют интерфейс BeanPostProcessor (примечание, примечание, впереди высокая энергия).
В Spring так много классов (косвенно), которые реализуют интерфейс BeanPostProcessor. Вы можете себе представить важность этого интерфейса, так как же этот интерфейс использовать? Это очень просто. Нам нужно только написать класс для реализации интерфейса BeanPostProcessor. .
Здесь я использую этот интерфейс для завершения динамических прокси JDK кастрированной версии инъекции:
Сначала определите интерфейс:
public interface Service {
void query();
}
Класс реализации:
@Component
public class ServiceImpl implements Service {
@Override
public void query() {
System.out.println("正在查询中");
}
}
Реализуйте интерфейс InvocationHandler:
public class MyInvationHandler implements InvocationHandler {
private Object target;
public MyInvationHandler(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进来了");
Object obj = method.invoke(target, args);
System.out.println("出去了");
return obj;
}
}
Реализуйте интерфейс BeanPostProcessor:
@Component
public class MyBeanPostProcess implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Object o = Proxy.newProxyInstance(MyBeanPostProcess.class.getClassLoader(),
bean.getClass().getInterfaces(), new MyInvationHandler(bean));
return o;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
класс конфигурации
@Configuration
@ComponentScan
public class AppConfig {
}
метод тестирования:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.getBean(Service.class).query();
}
}
результат операции:
Некоторые из них очень магические. Независимо от основного метода или класса реализации бизнеса, нет тени динамического прокси JDK, но динамический прокси действительно действует. Это магия интерфейса BeanPostProcessor. Фактически, Spring также. Динамический прокси достигается за счет реализации интерфейса BeanPostProcessor, который пока не показан.
BeanFactoryPostProcessor
BeanFactoryPostProcessor также является точкой расширения Spring. Программисты могут реализовать его, прочитать определение bean-компонента, а затем изменить его. Например, если мне нужно изменить область bean-компонента на прототип, я могу сделать это:
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.getBeanDefinition("repo").setScope("prototype");
}
}
@Repository
public class Repo {
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBeanDefinition("repo").getScope());
}
}
BeanFactoryPostProcessor предшествует BeanPostProcessor.
У бинов Singleton есть прототипы бинов
Что произойдет, если компонент-одиночка содержит компонент-прототип? Давайте напишем пример, чтобы увидеть:
@Configuration
@ComponentScan
public class AppConfig {
@Bean
@Scope("singleton")
public Single singleton(){
return new Single();
}
@Bean
@Scope("prototype")
public Prototype prototype(){
return new Prototype();
}
}
public class Single {
public Single(){
System.out.println("Single构造方法");
}
@Autowired
private Prototype prototype;
public Prototype getPrototype() {
return prototype;
}
public void setPrototype(Prototype prototype) {
this.prototype = prototype;
}
public void say() {
System.out.println(this);
prototype.say();
}
}
public class Prototype {
public Prototype(){
System.out.println("Prototype构造方法");
}
public void say() {
System.out.println(this);
}
}
@Component
public class Test {
@Autowired
Single single;
public void run() {
for (int i = 0; i < 5; i++) {
single.say();
}
}
}
Поскольку код длиннее, чтобы избежать прокрутки вверх и вниз вперед и назад, мои простые инструкции под этим кодом: Одиночные классы - это синглетоны, Прототип - это прототип, Одиночный класс опирается на Прототип, соответственно, чтобы добавить конструктор двух классов, напечатайте Одним словом, метод одного класса вызывает методы класса прототипа, два метода печатают это. Затем автоматическая инъекция Single Test Method, 5 циклов, вызовы метода Single class.
результат операции:
Этот результат явно проблематичен. Поскольку Single является синглтоном, он может выполнить конструктор только один раз, и каждый раз печатаемый объект один и тот же. Это не проблема, но Prototype является прототипом и запускает конструктор только один раз. объект также один и тот же, что является проблемой.
Как решить эту проблему?
ApplicationContextAware
Измените класс Single, чтобы он реализовывал метод setApplicationContext в интерфейсе ApplicationContextAware:
public class Single implements ApplicationContextAware {
public Single() {
System.out.println("Single构造方法");
}
private ApplicationContext context;
public void say() {
System.out.println(this);
context.getBean(Prototype.class).say();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
результат операции:
Проще говоря, это получение ApplicationContext с помощью метода setApplicationContext в интерфейсе ApplicationContextAware, присвоение его переменной контекста ApplicationContext в классе, а затем получение Bean-прототипа из контекста.
Этот метод должен зависеть от ApplicationContext.
lookup
@Component
@Scope("singleton")
public class Single {
public Single() {
System.out.println("Single构造方法");
}
public void say() {
System.out.println(this);
getPrototype().say();
}
@Lookup
public Prototype getPrototype() {
return null;
}
}
@Component
@Scope("prototype")
public class Prototype {
public Prototype(){
System.out.println("Prototype构造方法");
}
public void say() {
System.out.println(this);
}
}
результат операции:
Этот метод должен изменить bean-компонент определения в классе конфигурации на способ аннотации в классе.
Import
Импорт — это аннотация, предоставляемая Spring, которую можно использовать для введения другого класса в один класс и автоматического завершения регистрации другого класса:
@Configuration
@Import(ServiceImpl.class)
public class AppConfig {
}
public class ServiceImpl {
public void query() {
System.out.println("正在查询中");
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.getBean(ServiceImpl.class).query();
}
}
результат операции:
Видно, что хотя в классе ServiceImpl нет аннотации, ServiceImpl вводится через аннотацию Import в классе конфигурации AppConfig, и ServiceImpl регистрируется автоматически.
Возможно, просто использование аннотации Import усложнит код, поэтому его нужно использовать вместе, чтобы раскрыть его возможности.
ImportSelector
Давайте вернемся к параграфу, посвященному BeanPostProcessor, в котором мы определяем MyBeanPostProcess для завершения динамического прокси JDK, но давайте подумаем над вопросом, что, если нам не нужно использовать этот MyBeanPostProcess? Нам нужно удалить аннотацию Component на классе MyBeanPostProcess, нам нужно будет когда-нибудь использовать его снова, мы должны добавить его, если есть только один класс, это неплохо, но как могут быть десятки классов? Довольно хлопотно, можем ли мы справиться с этим единообразно в одном классе, какие бины нужно запускать так же, как Spring Boot? конечно может. Мы можем сделать это с помощью ImportSelector:
Сначала нам нужно определить класс, реализующий ImportSelector. Метод selectImports, который возвращает массив имен бинов, которые нужно привязать к этому классу:
public class AspectSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MyBeanPostProcess.class.getName()};
}
}
Давайте настроим еще одну аннотацию, аннотируем Import и введем указанный выше класс:
@Import(AspectSelector.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableAspect{
}
Обратите внимание на аннотацию AppConfig, там есть дополнительная аннотация EnableAspect:
@Configuration
@ComponentScan
@EnableAspect
public class AppConfig {
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.getBean(Service.class).query();
}
}
Затем мы удаляем аннотацию в MyBeanPostProcess и запускаем:
Когда нам не нужно использовать MyBeanPostProcess, просто удалите аннотацию EnableAspect в AppConfig, и все будет в порядке.
Это довольно крутой трюк, который широко используется в SpringBoot, например, включение управления транзакциями EnableTransactionManagement.
FactoryBean
FactoryBean и BeanFactory часто сравнивают между собой, потому что они слишком похожи, но похожи только внешне, на самом деле это совсем не одно и то же.
FactoryBean - это особый вид бобов. В дополнение к тому, чтобы быть Баэн, он также может создавать бобы. Подходит ли это имя FactoryBean?
FactoryBean — это интерфейс, нам нужно его реализовать:
@Component
public class MyFactoryBean implements FactoryBean {
public Object getObject() throws Exception {
return new DataSource();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
public class DataSource {
}
@Configuration
@ComponentScan
public class AppConfig {
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("myFactoryBean").getClass().getSimpleName());
System.out.println(context.getBean("&myFactoryBean").getClass().getSimpleName());
}
}
результат операции:
Мы видим, что на MyFactoryBean есть Компонент, который можно сканировать, но в DataSource ничего не добавляется, Само собой разумеется, что он не сканировался, но зарегистрирован, потому что реализует интерфейс FactoryBean. , метод getObject возвращает экземпляр DataSource, который можно понимать как Bean, созданный MyFactoryBean.
Давайте подробнее рассмотрим основной метод и результат выполнения: мы видим, что BeanName самого MyFactoryBean — это &myFactoryBean, а BeanName Bean-компонента, созданного MyFactoryBean, — это myFactoryBean.
Какая от этого польза? Детали построения bean-компонента могут быть скрыты. Если наш DataSource предоставлен третьей стороной, есть куча полей, которые нужно настроить, и куча зависимостей.Если мы его настроим, он вообще не может быть завершен. Лучший способ - оставить его на третья сторона обслуживания для настройки, но DataSource не может быть изменен. В настоящее время FactoryBean можно использовать для завершения, настройки источника данных в getObject и возврата. Mybatis, который мы часто используем, также использует интерфейс FactoryBean.
Spring слишком огромен, и многие функции используются нечасто. Я перечислю здесь лишь несколько мелких моментов. Добавляя те, которые мы часто используем, это может быть не одна десятая от Spring. Это уже оптимистично. .
Из-за нехватки места содержание этой главы на этом заканчивается.Среди них BeanPostProcessor, BeanFactoryPostProcessor, FactoryBean, Import, ImportSelector.Из-за них Spring стал более гибким и простым в использовании.