SSM-Spring(五)-注解的使用
注解实现bean标签(注解实现IoC)
-
常见的注解
1. @Component 相当于applicationContext.xml中的<bean> 默认会将类名的首字母小写形式作为bean的id,也可以通过value属性设置bean的id, 2. @Controller用于标识控制器层,@Service用于标识业务层,@Repository用于标识Dao层 3. @Scope:设置singleton、prototype 4. @Lazy:scope为singleton时延迟加载 5. 可以通过<context:component-scan> 设置需要扫描的包,可以扫描到所有的@Component注解。 1. @ComponentScan:相当于<context:component-scan>标签
-
举例使用
-
在applicationContext.xml文件中设置需要扫描那些包含注解的文件
<!--扫描指定包下面的所有注解--> <!--注意:只能扫描到domain下的文件,不能扫描dao、service、servlet层的文件,但是可以通过注解@ComponentScan来附加扫描其他文件--> <context:component-scan base-package="com.zh.domain"/>
-
domain下的person类注解如下:
//等价于bean标签,默认id为类名小写person @Component //指定bean的id为personx //@Component("personx") //在容器中是否唯一 @Scope("singleton") //等价于标签的lazy-init = true @Lazy //让入口xml文件,附加扫描其它的包 @ComponentScans({ @ComponentScan("com.zh.service"), @ComponentScan("com.zh.dao"), @ComponentScan("com.zh.servlet") }) public class Person { }
-
MyTest测试代码
ApplicationContext ctx = new ClassPathXmlApplicationContext( "applicationContext.xml"); System.out.println(ctx.getBean("person")); System.out.println(ctx.getBean("user")); System.out.println(ctx.getBean("userService"));
-
注解实现注入
@Autowired
:默认按照类型注入bean实例- 可以写在成员变量(不会调用setter)、setter、构造方法上
- 可以配合使用
@Qualifier
、@Named
:设置需要注入的bean的id - required设置为false:找不到对应的bean时不会抛出异常
@Value
- 用于注入String、基本类型、BigDecimal等类型
- 可以配合配置文件(比如properties)使用
${}
读取配置文件的内容
@PropertySources、@PropertySource
- 相当于
<context:property-placeholder>
- 相当于
- 指定构造方法
-
可以通过
@Autowired
指定创建bean时调用的构造方法public Student (){}; //创建bean时会调用这个构造方法 @Autowired public Student(@Value("zh") String name, @Qualifier("dog") Dog dog){ this.name = name; this.dog = dog; } //@Nullable会注入null值 @Autowired public Student(@Nullable String name, @Qualifier("dog") Dog dog){ this.name = name; this.dog = dog; }
-
-
举例使用:
//注解User类 @Component //导入配置文件,相当于<context:property-placeholder>,有了这个下面就可以使用配置文件中的动态引用了 @Value("${name}") @PropertySource("user.properties") public class User { //基本数据类型注入一个动态的值 @Value("${name}") private String name; //也可以直接放到成员变量上,不用放到set方法上,甚至不用写set方法 //@Autowired private Dog dog; //基本数据类型注入一个确定的值 //@Value("jack") //也可以放在set方法前面 //@Value("${name}") public void setName(String name) { this.name = name; } //自动注入 //@Autowired //required = false:找不到bean对象不会报错 @Autowired(required = false) //指定id值为dog的bean对象进行注入 @Qualifier("dog") public void setDog(Dog dog) { this.dog = dog; } } //user.properties文件内容 name=Rose
注解实现AOP
@Aspect
:用来标识一个切面类(切面类对象需要被放入到IoC容器中)@Around
:用来设置切入点(被注解的方法要有返回值、接收一个ProceedingJoinPoint参数)- 还需要加上
<aop:aspectj-autoproxy>
标签,替代之前的<aop:config>
- 同样有proxy-target-class属性设置AOP的底层实现方案(强制使用CGLib、按照默认做法)
<!--替代之前的<aop:config>--> <aop:aspectj-autoproxy proxy-target-class="true"/>
举例使用
-
service层如下:
//接口 public interface UserService { void list(); } //实现 @Service("userService") public class UserServiceImpl implements UserService { public void list() { System.out.println("UserServiceImpl - list"); } }
-
添加切面类
//代表一个切面类 @Aspect //放入到spring容器中去 @Component public class DefaultAspect { //可以使用@Pointcut复用切入点,这里设置好切入点,其他地方引用。 //1. 设置要切入的类,给当前类的所有方法都切入 @Pointcut("within(com.zh.service.impl.UserServiceImpl)") //2. 用一个空方法来代表切入点 public void pc() {} //设置切入点,也可以直接使用@Around("within(com.zh.service.impl.UserServiceImpl)") @Around("pc()") //附加代码:必须要接收一个ProceedingJoinPoint类型参数,用来拿到目标对象,执行目标方法;而且必须要有返回值 public Object log(ProceedingJoinPoint point) throws Throwable { System.out.println("log-----------------------1"); // 调用目标方法 Object ret = point.proceed(); System.out.println("log-----------------------2"); return ret; } //设置切入点 @Around("pc()") public Object watch(ProceedingJoinPoint point) throws Throwable { System.out.println("watch-----------------------1"); // 调用目标方法 Object ret = point.proceed(); System.out.println("watch-----------------------2"); return ret; } }
-
applicationContext.xml
<!--让spring扫描这个包下所有的类,用来解析注解--> <context:component-scan base-package="com.zh"/> <!--替代之前的<aop:config>--> <aop:aspectj-autoproxy proxy-target-class="false"/>
如何将自定义的附加代码与注解混合使用
-
自定义附加代码
public class LogInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("LogInterceptor-----------------------1"); // 调用目标方法 Object ret = invocation.proceed(); System.out.println("LogInterceptor-----------------------2"); return ret; } }
-
通过applicationContext.xml配置,将附加代码也切入到注解类设置的切入点(
com.zh.service.impl.UserServiceImpl
)中<!--xml与注解混合使用--> <bean id="logInterceptor" class="com.zh.aop.LogInterceptor"/> <aop:config> <!--直接使用注解中的切入点 将自定义的附加代码,切入到注解的切面类设置的切入点中 --> <aop:pointcut id="pc" expression="com.zh.aop.DefaultAspect.pc()"/> <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/> <!--替代上面的两句--> <!-- <aop:advisor advice-ref="logInterceptor" pointcut="com.zh.aop.DefaultAspect.pc()"/>--> </aop:config>
测试结果
```
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"applicationContext.xml");
UserService service = ctx.getBean("userService", UserService.class);
service.list();
//打印
LogInterceptor-----------------------1
log-----------------------1
watch-----------------------1
UserServiceImpl - list
watch-----------------------2
log-----------------------2
LogInterceptor-----------------------2
```
注解实现事务管理
@Transactional
- 可以在需要进行事务管理的类(比如Service) 上使用这个注解, 代表所有方法都会自动管理事务
- 可以在需要进行事务管理的方法上使用这个注解(可以覆盖类中@Transactional的配置)
- 可以设置isolation、propagation、imeout、rollback For、noRollbackFor、readOnly等属性
- 使用
<tx:annotation-driven>
取代之前的<tx:advice>
、<aop:config>
- 同样有proxy-target-class属性设置AOP的底层实现方案
代码举例
-
之前讲解的事务管理如下:
<!-- 事务管理器 --> <bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 附加代码:事务管理代码 --> <tx:advice id="txAdvice" transaction-manager="txMgr"> <tx:attributes> <tx:method name="*"/> <tx:method name="list*" propagation="SUPPORTS"/> </tx:attributes> </tx:advice> <!-- 切面 --> <aop:config> <aop:pointcut id="pc" expression="within(com.zh.service..*)"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/> </aop:config>
-
在service层设置事务管理注解
@Service("skillService") //代表所有方法都需要添加事务管理 @Transactional //可以设置事务的属性 //@Transactional(isolation = ) public class SkillServiceImpl implements SkillService { private SkillDao dao; @Autowired public void setDao(SkillDao dao) { this.dao = dao; } @Override //针对某个特定的方法,设置特别的属性值 @Transactional(propagation = Propagation.SUPPORTS) public List<Skill> list() { return dao.list(); } @Override public boolean save(Skill skill) { return dao.save(skill); } @Override public boolean update(Skill skill) { return dao.update(skill); } public void test(Skill skill) throws Exception { dao.save(skill); throw new RuntimeException(); } }
-
在applicationContext.xml配置如下
<!-- 事务管理器 --> <bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--设置注解驱动 取代之前的<tx:advice>、`<aop:config> --> <tx:annotation-driven transaction-manager="txMgr"/> <!--让spring扫描这个包下所有的类,用来解析注解--> <context:component-scan base-package="com.zh"/>
@Configuration、@Bean
@Configuration
:使用此注解的类,可以取代applicationContext.xml- 它也是一个
@Component
, 所以可以通过component-scan扫描
- 它也是一个
- 可以在
@Configuration
类中,使用@Bean
修饰方法,进行bean对象的创建- 默认情况下,方法名就是bean的id。也可以通过name、value属性设置bean的id
- 可以配合
@Scope
设置bean的创建次数 - 默认情况下,当IoC容器创建的时候,会调用所有
@Bean
修饰方法,生成对象放入到容器中
//com.zh.cfg.BeanConfig //使用此注解的类,可以取代applicationContext.xml @Configuration public class BeanConfig { //使用@Bean修饰方法,进行bean对象的创建 // 默认情况下,方法名就是bean的id。也可以通过name、value属性设置bean的id @Bean //通过name属性设置id //@Bean(name = "skillx") //可以配合@Scope设置bean的创建次数 //@Scope("prototype") //@Lazy public Skill skill() { Skill skill = new Skill(); return skill; } }
- 下面讲解:一个对象注入为bean对象时、基本数据类型时,如何使用注解
- 相当于之前xml文件中bean标签的类,注入属性为对象、基本数据类型,使用注解如何实现
- 注意下面讲的所有方法都在BeanConfig中
@Configuration修饰类中注入bean对象的3种方式
-
方式一:如果bean属性本身有@Autowired,那么直接new原来对象即可(前提条件是IoC容器中必须有这个bean参数对象)
//Dog类 //表明可以通过scan将该类创建对象并存放到IoC容器中,保证容器中有dog对象 @Component public class Dog { } //bean属性本身有@Autowired,但是没有写@Component public class Person { private Dog dog; @Autowired public void setDog(Dog dog) { this.dog = dog; } } //直接new对象即可 @Configuration public class BeanConfig { //尽管Person类没有用@Component修饰,但是这句话会将person对象放入容器 @Bean public Person person(){ return new Person(); } }
-
方式二:Spring会利用@Autowired技术,自动注入bean给@Bean方法的参数(前提条件是IoC容器中必须有这个bean参数对象)
//保证容器中有dog对象 @Bean public Dog dog() { return new Dog(); } @Bean //参数自动注入 public Person person(Dog dog) { Person person = new Person(); person.setDog(dog); return person; }
-
方式三:直接调用
@Bean
方法来注入@Bean public Dog dog() { return new Dog(); } @Bean //不传参直接内部调用@Bean方法 public Person person() { Person person = new Person(); //直接调用bean方法来注入 person.setDog(dog()); return person; } @Bean public Student student(){ Student student = new Student(); //这个dog与Person的dog是同一个对象 student.setDog(dog()); return student; }
- 这里有个细节:
@Bean
方法被直接调用多次,也能保证是singleton(同一个对象)- 上面2个创建直接调用dog(),为什么是同一个对象?
- 因为
@Configuration
底层使用了CGLib动态代理,对@Bean
方法进行了增强处理。 - 调用dog()时,本质不是BeanConfig对象调用的,而是他的继承子类(CGLib本质是继承)去调用的,比如BeanConfigSub,该子类对所有父类的bean方法做了过滤,如果当前容器中有对象,就不在调用父类的方法,直接返回
- 这里有个细节:
FactoryBean
- 这个是复杂对象的注入,请回看spring第一节“复杂对象的注入-FactoryBean”
-
创建一个DogFactoryBean类
//DogFactoryBean对象会放入到容器中 @Component public class DogFactoryBean implements FactoryBean<Dog> { //dog对象也会放到容器中 @Override public Dog getObject() throws Exception { return new Dog(); } @Override public Class<?> getObjectType() { return Dog.class; } }
-
BeanConfig(方式一:)
@Bean public DogFactoryBean dog() { return new DogFactoryBean(); } //直接通过@Bean方法dog()拿到DogFactoryBean对象,然后拿到dog对象 @Bean public Student student() throws Exception { Student student = new Student(); student.setDog(dog().getObject()); return student; }
-
测试1
System.out.println(ctx.getBean("dog"));
- 表面上看,
@Bean
方法返回的是DogFactoryBean类型对象 - 由于
@Configuration
底层使用CGLib - 对
@Bean
进行了增强处理 - 会调用DogFactoryBean的getObject方法
- 表面上看,
-
方式2:直接到容器中拿DogFactoryBean对象
//直接不写dog()方法,这样容器中就没有id为dog的对象 //根据容器中已经存储的bean对象,自动注入参数 @Bean public Person person(DogFactoryBean bean) throws Exception { Person person = new Person(); person.setDog(bean.getObject()); return person; } @Bean public Student student(DogFactoryBean bean) throws Exception { Student student = new Student(); student.setDog(bean.getObject()); return student; }
-
测试2
//此时打印的是dog对象,而不是dogFactoryBean对象,会自动调用DogFactoryBean类的getObject方法,拿到的是方法的返回值 System.out.println(ctx.getBean("dogFactoryBean")); //返回的才是dogFactoryBean对象 System.out.println(ctx.getBean("&dogFactoryBean"));
注入其他类型
-
配置文件
//skill.properties name=Rose level=20
-
BeanConfig
@Configuration //获取配置文件数据 @PropertySource("skill.properties") public class BeanConfig { //从配置文件中取值,然后注入给BeanConfig的成员变量 @Value("${name}") private String name; @Value("${level}") private int level; @Bean public Skill skill() { Skill skill = new Skill(); //直接写固定值 //skill.setName("jack"); //skill.setLevel(1); //从当前类的成员变量中注入 skill.setName(name); skill.setLevel(level); return skill; } }
创建工厂的入口
- 如果创建工厂的入口是XML,一般就用ClassPathXmlApplicationContext
- 可以通过
<context:component-scan>
去扫描注解信息 - 可以通过
<import>
导入其他XML配置文件 - 这样就可以实现入口是xml,然后xml与注解混合使用的方式
//1. 入口:通过代码加载xml,创建容器 ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.applicationContext.xml文件 //扫描所有注解的包下的类 <context:component-scan base-package="com.zh"/> <!-- <import resource="其他xml文件"-->
- 可以通过
- 如果创建工厂的入口是注解,一般就用AnnotationConfigApplicationContext
- 即入口可不从加载xml文件来作为入口,直接通过扫描注解
- 可以通过
@Import、@ComponentScan
扫描其他注解信息- ComponentScan:只能扫描到带有
@Component
修饰的类;@Configuration
也可以,打开源码可以看到它本质也是@Component
- 即只能扫描用
@Component
相关修饰的类 - 用AnnotationConfigApplicationContext作为入口直接加载的类,可以不用
@Component
修饰
- ComponentScan:只能扫描到带有
- 可以通过
@ImportSource
导入其他XML配置文件 - 注意: 通过
@Import
导入创建的bean的id是全类名
//可以直接加载这边包下的所有的类,不管类是否用@Component相关修饰 ctx = new AnnotationConfigApplicationContext("com.zh"); //单独加载一个类到容器中,不管这类是否用@Component相关修饰 ctx = new AnnotationConfigApplicationContext(Person.class);
举例
-
通过代码设置入口类(可以传递多个),但容器中只会存在这几个类对应的对象,除非这几个类中使用
@Import
、@ComponentScan
注解,扫描其他类//类,没有用@Component修饰 public class Dog {} //设置入口:只设置一个类 ctx = new AnnotationConfigApplicationContext(Dog.class); //打印有值 System.out.println(ctx.getBean("dog")); //打印无值,容器中没有person对象 System.out.println(ctx.getBean("person"));
-
可以通过
@Import
扫描其他注解信息//类 @Component //@Import注解,告诉Spring扫描其他的类 @Import(Person.class) //@Import({Person.class, BeanConfig.class}) public class Dog {} //设置入口:只设置一个类 ctx = new AnnotationConfigApplicationContext(Dog.class); //打印有值 System.out.println(ctx.getBean("dog")); //打印无值 System.out.println(ctx.getBean("person")); //打印有值:通过Import导入的扫描出来的类,在容器中创建的对象的id不是类名的小写,而是全类名 System.out.println(ctx.getBean("com.zh.domain.Person"));
-
可以通过
@ComponentScan
扫描其他注解信息//类 @Component //只能扫描这个包下的所有用@Component相关修饰类的注解 @ComponentScan("com.zh") public class Dog {} //没有用@Component相关修饰 public class FixConfig {} //有用@Component相关修饰 @Component public class Person {} //设置入口:只设置一个类 ctx = new AnnotationConfigApplicationContext(Dog.class); //打印有值 System.out.println(ctx.getBean("dog")); //打印有值 System.out.println(ctx.getBean("person")); //打印无值 System.out.println(ctx.getBean("fixConfig"));
-
可以通过
@ImportSource
导入其他XML配置文件(这样可以实现注解与xml混合编写代码)<!-- 纯注解类作为入口********** --> @Configuration //注解与xml混合开发 @ImportResource("applicationContext.xml") public class FixConfig {} <!--XML开发**********--> //applicationContext.xml <bean id="userService" class="com.zh.service.impl.UserServiceImpl" p:name="zh666"/> //UserServiceImpl类 public class UserServiceImpl { private String name; public void setName(String name) { this.name = name; } } <!-- 设置入口**********--> //设置入口:只设置一个类 ctx = new AnnotationConfigApplicationContext(FixConfig.class); //有值 System.out.println(ctx.getBean("userService"));
-
通常情况下,最常用的使用方式是通过一个核心类作为入口,再在这个核心类中关联其他类
//1.入口加载一个核心类 ctx = new AnnotationConfigApplicationContext(BeanConfig.class); //2.核心类BeanConfig @Configuration //扫描这个包下的所有用@Component相关修饰的类的注解 @ComponentScan("com.zh") public class BeanConfig {} //3. 其他类 @Configuration //注解与xml混合开发 @ImportResource("applicationContext.xml") public class FixConfig {} //dog类 @Component public class Dog {} ...
bean的创建方式总结
- 在Spring中,bean有3种常见的创建方式(将一个对象放入到IoC容器中)
@Component
:常用于源码可修改、创建过程比较简单(直直接调用构造方法即可)的类型- 直接在一个类前面使用,然后通过xml标签或者注解扫描
- id值默认就是类名的首字母小写,也可以
@Component("id值")
指定id值
@Bean
:常用于源码不可修改的类型 (比如第三方库的类型)或者创建过程比较复杂的类型- 在
@Configuration
修饰的类中使用 -
id值就是方法的名称
//创建过程比较复杂的类型 @Bean public SqlSession session(){ SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(null); return factory.openSession(); }
- 在
<bean>
:适用于所有类型- 就是在xml中使用
- id值就是主动设置的值
- 当上述三种方式bean的id相同时, 优先级如下
<bean>
大于@Bean
大于@Component
- 因为
<bean>
可以用于覆盖以前@Bean
、@Component
的内容,所以减少@Bean
、@Component
代码的变动- 通过
<bean>
全局设置,可以不用到某个具体类中去修改代码 -
假设一个项目使用纯注解开发,但是我不想通过去修改原来的代码来进行修改,那么就可以创建一个xml文件(applicationContext.xml),然后通过
<bean>
修改那个类的东西,然后再创建一个类,使用注解与xml混合来实现,将这个类与xml自动导入到IoC容器中@Configuration //注解与xml混合开发 @ImportResource("applicationContext.xml") public class FixConfig {}
- 通过